SpringBoot实现自定义包扫描_springboot自定义扫描包_长沙郭富城的博客-程序员宅基地

技术标签: spring-boot  

SpringBoot实现自定义包扫描

最近很好奇在SpringBoot项目上加@MapperScan(basePackages = "xxx") 注解就能扫描到执行的包下面的东西。于是研究了一下Mybatis怎么实现的。大致是根据Mybatis依葫芦画瓢

于是点开@MapperScan类,看到类结构如下:


@Retention(RetentionPolicy.RUNTIME)
@Target({
    ElementType.TYPE})
@Documented
@Import({
    MapperScannerRegistrar.class})  //MapperScannerRegistrar这个类才是真正的注册逻辑
@Repeatable(MapperScans.class)
public @interface MapperScan {
    
    String[] value() default {
    };

    String[] basePackages() default {
    };

    Class<?>[] basePackageClasses() default {
    };
	
    ...
}


然后点开MapperScannerRegistrar,看一下类结构


//当使用@Import标签,实现ImportBeanDefinitionRegistrar 接口,那么可以自定义扩展beanDefinition
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
    
    //资源加载器
    private ResourceLoader resourceLoader;


    public void setResourceLoader(ResourceLoader resourceLoader) {
    
        this.resourceLoader = resourceLoader;
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
        //从启动类上面获取MapperScan注解
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        if (mapperScanAttrs != null) {
    
            //存在的话那么注册自己的BeanDefinitions
            this.registerBeanDefinitions(mapperScanAttrs, registry);
        }

    }

    //已将不重要的内容删除
    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
    
        //自定义注解扫描器
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
    
           //这里是对应处理bean scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
        }
       //获取注解basePackages value   basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
        scanner.registerFilters();
        //获取到包,开始进行扫描, 
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
}

这里是doScan的逻辑,Spring的代码中,一般已do开头的都是具体做事情的,这里返回扫描包下面的的beanDefinitions集合,如果存在的话,那么会调用对应的BeanNameGenerator 方法,对应上面scanner.setBeanNameGenerator

    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        return beanDefinitions;
    }

大概思路了解了,那我们也整一个

  1. 定义启动类需要配置的扫描注解

    
    import org.springframework.context.annotation.Import;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * ClassName: MyAOP
     * @Description:
     * @author leegoo
     * @date 2020年03月14日
     */
    @Retention(RetentionPolicy.RUNTIME)//注意用这个注解才能在运行时使用反射
    @Target({
          ElementType.TYPE})
    @Documented
    @Import({
          CustomerScanRegister.class})
    public @interface CustomerScan {
          
        //扫描包路径
        String[] basePackages() default {
          };
        //扫描类
        Class<?>[] basePackageClasses() default {
          };
    }
    
    
  2. 定义CustomerScanRegister以及实现

    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.BeanFactoryAware;
    import org.springframework.beans.factory.config.BeanDefinitionHolder;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.ResourceLoaderAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.annotation.AnnotationAttributes;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.lang.NonNull;
    import org.springframework.util.ClassUtils;
    import org.springframework.util.StringUtils;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    
    public class CustomerScanRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanFactoryAware {
          
        private ResourceLoader resourceLoader;
    
        private BeanFactory beanFactory;
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
          
            this.beanFactory = beanFactory;
        }
    
        @Override
        public void setResourceLoader(@NonNull ResourceLoader resourceLoader) {
          
            this.resourceLoader = resourceLoader;
        }
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,@NonNull BeanDefinitionRegistry registry) {
          
            //这里是获取cn.withmes.springboot.my.aop.SpringBootMyAopApplication类上对应的注解
            //MergedAnnotations annotations = importingClassMetadata.getAnnotations();
            //这里判断是否存在MyAOP注解
            AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(CustomerScan.class.getName()));
            if (mapperScanAttrs == null)  return;
    
            this.registerBeanDefinitions(mapperScanAttrs, registry);
        }
    
    
        private  Set<BeanDefinitionHolder> registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
          
            List<String> basePackages = new ArrayList<>();
            //取到所有属性的值
            basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
            basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
            CustomerScanner scanner = new CustomerScanner(registry);
            scanner.setBeanNameGenerator(( beanDefinition,beanDefinitionRegistry)->{
          
                String beanClassName = beanDefinition.getBeanClassName();
                try {
          
                    Class<?> clz = Class.forName(beanClassName);
                    MyService at = clz.getAnnotation(MyService.class);
                    if (null == at) return null;
                    //如果@MyService没有指定名字,那么默认首字母小写进行注册
                    if (at.name().equalsIgnoreCase("")  ) {
          
                        String clzSimpleName = clz.getSimpleName();
                        String first = String.valueOf(clzSimpleName.charAt(0));
                        return clzSimpleName.replaceFirst(first,first.toLowerCase());
                    }
                    return at.name();
                } catch (ClassNotFoundException e) {
          
                    e.printStackTrace();
                    return null;
                }
            });
            if(resourceLoader != null){
          
                scanner.setResourceLoader(resourceLoader);
            }
            
            return scanner.doScan(StringUtils.toStringArray(basePackages));
        }
    
    
    
    
  3. 自定义扫描器

    
    import org.springframework.beans.factory.config.BeanDefinitionHolder;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
    import org.springframework.core.type.filter.AnnotationTypeFilter;
    
    import java.util.Set;
    
    
    public class CustomerScanner extends ClassPathBeanDefinitionScanner {
          
    
        public CustomerScanner(BeanDefinitionRegistry registry) {
          
            super(registry, false);
        }
    
        @Override
        public Set<BeanDefinitionHolder> doScan(String... basePackages) {
          
            //添加过滤条件,这里是只添加了@MyService的注解才会被扫描到
            addIncludeFilter(new AnnotationTypeFilter(MyService.class));
            return super.doScan(basePackages);
        }
    
    }
    
    
  4. 写一个接口和两个实现类,试下不同的方式注入(这里把所有类的代码放在一起了)

    
    public interface UserService {
          
        User findUser(Integer id) ;
    }
    
    
    @MyService // 使用自定义注解,注入spring容器
    public class UserServiceImpl implements UserService {
          
    
        @Resource
        private Data data;
    
        @Override
        public User findUser(Integer id)  {
          
            return data.users.get(id);
        }
    
    }
    
    @MyService(name = "lsUser") // 使用自定义注解,注入spring容器
    public class UserServiceImpl2 implements UserService {
          
    
        @Resource
        private Data data;
    
        @Override
        public User findUser(Integer id)  {
          
            return data.users.get(id);
        }
    
    }
    
    
    //实现CommandLineRunner,应用初始化后,去执行一段代码块逻辑,这段初始化代码在整个应用生命周期内只会执行一次
    @Service
    public class Data  implements CommandLineRunner {
          
        public Map<Integer, User> users = new HashMap<>(
    
        );
    
    
        @Override
        public void run(String... args) throws Exception {
          
            users.put(1, new User(1, "小红"));
            users.put(2, new User(2, "小明"));
            users.put(3, new User(3, "小三"));
            System.out.println("初始化数据:"+users);
        }
    
        public User getUsers(Integer id) {
          
            return users.get(id);
        }
    
    }
    
    
    
  5. 开始测试

    @SpringBootTest
    class SpringBootMyAopApplicationTests {
          
    
        //这里userServiceImpl 会报红,但是还是能够执行,因为我们没有使用spring注解进行Bean注入,所以会提示我们可能找不到bean
        @Resource(name = "userServiceImpl")
        private UserService  userService;
    
        @Resource(name = "lsUser")
        private UserService  lsUser;
    
        @Test
        void testCustomerAnnotation() {
          
            User user = userService.findUser(1);
            System.out.println("user:"+user); //user:User{id=1, name='小红'}
            User ls = lsUser.findUser(2);
            System.out.println("ls:"+ls);//ls:User{id=2, name='小明'}
        }
    
    }
    

代码已上传github(github主页:https://github.com/q920447939/java-study)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq920447939/article/details/104874757

智能推荐

爬虫、蜘蛛、机器人有什么区别?_蜘蛛机器人和爬虫机器人的区别_微wx笑的博客-程序员宅基地

蜘蛛:我最早知道这类程序存在,听到的名字就是蜘蛛;最早知道的蜘蛛应该就是BaiDuSpider;怎么知道的?现在已经记不清了,大概是当时在做网站流量统计的时候,由于记录的网站用户请求的UserAgent内容,所以在访问记录中可以看到它留下的足迹。网络蜘蛛即Web Spider,是一个很形象的名字。把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛。爬虫:这个条目所描述的内容英文名叫..._蜘蛛机器人和爬虫机器人的区别

实验设计与排错之二RIPI、II的区别_weixin_34419326的博客-程序员宅基地

一、动态路由动态路由协议有灵活等很多优点,但是缺点也有,比如占用了额外的带宽,CPU负荷高。管理距离(Administrative Distances):0到255之间的1个数,它表示一条路由选择信息源的可信性值。该值越小,可信性级别越高。0为最信任,255为最不信任。即没有从这条线路将没有任何流量通过。假如1台路由器收到远端的2条路由更新,路由器将检查管理距离,管理距离值低...

Clover引导简明教程_cover引导_徐念安的博客-程序员宅基地

如何打开啰嗦模式进行排错开机进入clover引导界面, 在要引导的分区卷标上按 空格 即可进入 勾选以下选项: 选择 Boot macOS with selected options 启动 出现错误画面拍照发群里寻求帮助。Clover是什么什么是Clover(三叶草)呢?显然它不是指的草地里用来喂牛的草啦。Clover是一个软件,是一个新型的启动器,它能够让普通的PC上用上M..._cover引导

每天更新美图的图片地址_每天提供更换图片的地址_从不打鱼I专注晒网的博客-程序员宅基地

记一下,怕忘了(必应每日壁纸) https://link.zhihu.com/?target=http%3A//area.sinaapp.com/bingImg/_每天提供更换图片的地址

让字典保持有序 OrderedDict控制元素的顺序———— OrderedDict类_禾攻城狮的博客-程序员宅基地

1、问题:我们想创建一个字典,同时对字典做迭代或者序列化操作时,也恩呢该控制其中元素的顺序。2、解决:要控制字典中的顺序,可以使用collections模块中的OrderedDict类。当对字典做迭代时。它会严格照元素初始添加的顺序进行。代码:from collections import OrderedDict d=OrderedDict()d['foo']=1d['bar']=2d['...

楪祈机器人_饥荒楪祈mod下载 饥荒mod大全_weixin_39965490的博客-程序员宅基地

饥荒数据库为您提供饥荒物品代码、物品属性介绍、物品的合成和获取方法,快来一起了解下吧!点击查看>:饥荒数据库饥荒mod-楪祈下载;mod也称游戏模组,就是将游戏的数据进行修改或是增加新的内容,今天小编跟大家分享饥荒mod-楪祈的下载,希望大家会喜欢。ps:手机版现不支持mod,想尝试的玩家请移步PC端。作品名称:楪祈MOD介绍:人物属性健康:77饥饿:77理智:177角色:每次吃肉类食物升一..._饥荒楪祈mod介绍

随便推点

docker上运行python容器与本机carla交互_gym carla-程序员宅基地

docker上运行python容器与本机carla交互安装docker开始安装创建python镜像并运行容器安装docker开始安装由于apt官方库里的docker版本可能比较旧,所以先卸载可能存在的旧版本:$ sudo apt-get remove docker docker-engine docker-ce docker.io更新apt包索引:$ sudo apt-get update安装以下包以使apt可以通过HTTPS使用存储库(repository):$ sudo apt-ge_gym carla

如何在myeclipse上使用git(码云)_weixin_30791095的博客-程序员宅基地

在合适的位置创建自己版本库!什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。第一步:创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录:第二步..._myeclipse 使用git

FFmpeg:AVPacket结构体分析_av传送门_SuperDali的博客-程序员宅基地

AVPacket文档地址:传送门在AVPacket结构体的说明部分:有这么一段描述,足够说明它的作用和重要性。该结构存储压缩数据。 它通常由解复用器导出,然后作为输入传递给解码器,或作为编码器的输出接收,然后传递给复用器。对于视频而言,它通常应包含一个压缩帧。 对于音频,它可能包含几个压缩帧。 允许编码器输出空包,没有压缩数据,只包含不重要的附加信息数据。例如在编码结束时更新一些流参数。..._av传送门

超级无敌简单易懂的海明码的校验和纠错原理与实现_豆沙粽子好吃嘛!的博客-程序员宅基地

最近和朋友的聊天涉及到了海明码纠错,先来康康海明纠错码到底是什么海明码Hamming Code,电信领域的一种线性调试码,由于编码简单,广泛应用于内存(RAM)。编码原理若海明码长为n,信息位数为k,则需要插入r个监督位校验码。如果想要r个校验码能构成r个关系式来指示出错码的n个可能位置,则需要2r−1≥n2^r - 1 \geq n2r−1≥n即为2r−r≥k+12^r - r ...

优美图案c语言程序,C语言经典例题100例——C语言练习实例65解答(一个最优美的图案)..._WZWTWT的博客-程序员宅基地

题目:一个最优美的图案(在TC中实现)。程序分析:无。程序源代码:// 百宝箱工作室官方网址 http://www.baibaox.com// 百宝箱工作室业务介绍 http://www.baibaox.com/BusinessIntroduction.html#include "graphics.h" //VC6.0中是不能运行的,要在Turbo2.0/3.0中#include "math.h...

推荐文章

热门文章

相关标签