• 曹工说Spring Boot源码(28)-- Spring的component-scan机制,让你自己来进行简单实现,怎么办


    写在前面的话

    相关背景及资源:

    曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享

    曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解

    曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下

    曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

    曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

    曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

    曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)

    曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

    曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

    曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

    曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)

    曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)

    曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)

    曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成

    曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)

    曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

    曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

    曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)

    曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)

    曹工说Spring Boot源码(20)-- 码网恢恢,疏而不漏,如何记录Spring RedisTemplate每次操作日志

    曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了

    曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了

    曹工说Spring Boot源码(23)-- ASM又立功了,Spring原来是这么递归获取注解的元注解的

    曹工说Spring Boot源码(24)-- Spring注解扫描的瑞士军刀,asm技术实战(上)

    曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

    曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎

    曹工说Spring Boot源码(27)-- Spring的component-scan,光是include-filter属性的各种配置方式,就够玩半天了

    工程代码地址 思维导图地址

    工程结构图:

    概要

    本讲相对独立,我们也不爱说废话,直接说本讲要做啥。

    大家知道,@Component-scan注解,在注解时代,最主要的用法就是指定一个package的名称,然后spring就会去对应的包下面,扫描注解了@Controller、@Service、@Repository等注解的类,然后注册为bean。

    spring boot时代,这个注解则站到了幕后,如下:

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      @SpringBootConfiguration
      @EnableAutoConfiguration
      @ComponentScan(
          excludeFilters = {
            @Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),    
            @Filter( type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})
          }
      )
      public @interface SpringBootApplication {
    }
    

    总之,这个注解大家再熟悉不过了。我们本讲,最核心的目标是,让大家更懂spring,方法呢,就是让我们自己来实现以下目标:

    1. 定义main类

      @MyConfiguration
      @MyComponentScan(value = "org.springframework.test")
      public class BootStrap {
          public static void main(String[] args) {
               ...
      }
      

      这个上面定义了2个自定义的注解,都加了个前缀"My"。下面简单看看。

      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface MyConfiguration {
      	
      }
      

      这个的效果,类似于@configuration,表示我们是一个配置类,配置类上一般会有一堆其他注解,来引入其他bean definition。

      然后是MyComponentScan 注解:

      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface MyComponentScan {
      
          /*
           * 要扫描的包名
           */
          String value();
      
      }
      
    2. 我们的target包下,有一个需要我们扫描的bean,如下:

      package org.springframework.test;
      
      @MyComponent
      @MyComponentScan(value = "org.springframework.test1")
      public class PersonService {
          private String personname1;
      }
      

      这里使用MyComponent注解,这个注解,用来注解我们要扫描为bean的那些类。比如这里,我们希望PersonService这个类,被扫描为bean。

      其次,我们还定义了一个@MyComponentScan(value = "org.springframework.test1"),这个主要是:我们要能够支持递归处理。

      当然,现在可以先忽略,权且当它不存在。

    3. 最终的测试效果如下:

      import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
      import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
      import org.springframework.beans.factory.support.RootBeanDefinition;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      import org.springframework.custom.MyConfigurationClassPostProcessor;
      import org.springframework.custom.annotation.MyComponentScan;
      import org.springframework.custom.annotation.MyConfiguration;
      import org.springframework.test1.AnotherPersonService;
      
      @MyConfiguration
      @MyComponentScan(value = "org.springframework.test")
      public class BootStrap {
          public static void main(String[] args) {
              AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
              AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(BootStrap.class);
              context.registerBeanDefinition(BootStrap.class.getName(), beanDefinition);
      
              /**
               * 注册一个beanFactoryPostProcessor,用来处理MyComponentScan注解
               */
              RootBeanDefinition def = new RootBeanDefinition(MyConfigurationClassPostProcessor.class);
              def.setSource(null);
              context.registerBeanDefinition(MyConfigurationClassPostProcessor.class.getName(),
                      def);
      
              context.refresh();
      
              PersonService bean = context.getBean(PersonService.class);
              System.out.println(bean);
          }
      }
      

      输出如下:

      12:39:27.259 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'personService'
      org.springframework.test.PersonService@71ba5790

    可以看到,我们的目标就是以上这样。为了实现这个目标,其实我们是仿照spring的实现,自己ctrl c/v了一把,其中进行了大量的简化。

    实现思路

    思路基本等同于spring的实现,因为本系列教程的目的就是让大家更懂Spring,所以没必要另辟蹊径。

    用@MyConfiguration注解一个起点配置类

    指定一个用 @MyConfiguration 注解的类,这个类一开始就会被注册为bean definition,类似于spring boot中的启动类,大家知道,spring boot中,启动类也是间接注解了 @SpringBootConfiguration,而 @SpringBootConfiguration呢,大家看看:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    
    }
    

    该注解,是注解了@configuration。

    其实就是手动指定一个配置类,这个配置类,源码里好像是叫做startUp config,作为一个起点,通过这个起点,我们能发现在这个起点类上的,更多的元注解,比如,在我们公司的真实项目中,启动类就配置了一堆东西:

    @SpringBootApplication
    @EnableTransactionManagement
    @EnableAspectJAutoProxy(exposeProxy = true)
    @MapperScan("com.xxx.cad.mapper")
    @ComponentScan("com.xxx")
    @EnableFeignClients
    //@Slf4j
    @Controller
    @EnableScheduling
    public class CadWebService {
    

    这个类,就是我们这里的起点类。这个起点类,然后被注册为一个bean。

    注册一个BeanDefinitionRegistryPostProcessor,用来处理配置类上的@MyComponentScan,以发现更多bean

    大家知道@configuration配置类,是怎么被处理的呢?就是通过org.springframework.context.annotation.ConfigurationClassPostProcessor

    这个类呢,实现了如下接口:

    public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    
    	/**
    	 * Modify the application context's internal bean definition registry after its
    	 * standard initialization. All regular bean definitions will have been loaded,
    	 * but no beans will have been instantiated yet. This allows for adding further
    	 * bean definitions before the next post-processing phase kicks in.
    	 * @param registry the bean definition registry used by the application context
    	 * @throws org.springframework.beans.BeansException in case of errors
    	 */
    	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
    
    }
    

    这个接口的这个方法的调用时机,是在:截止目前,通过配置(不论是xml,还是像上面第一步这样,去手动注册bean definition)的方式,能够找到的bean definition都已经找到了;本来,下一步就是找出其中的要eager-init的单例bean,去初始化了。

    但是呢,在这之前,我们还有一个步骤,也算是一个扩展点吧,可以让我们去修改目前的bean definition集合。

    如果举个通俗的例子,大概是这样的,比如,一个公司,组织大家出去玩,大家自愿报名,一开始假设报了10个人,预定周六出发;在此之前呢,公司再让大家确认一下,大家可以

    1. 增,带家属,带男女朋友;
    2. 删,自己不去了
    3. 改,我周六好像还要写bug,但我另一个同事没啥事,他可以去

    而要实现这些,只要你实现前面我们提到的那个接口的方法即可,大家可以再观察下这个方法:

    	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
    
    

    入参是registry,也就是当前的报名表,报名表都给你了,你还有啥不能干的?

    public interface BeanDefinitionRegistry extends AliasRegistry {
    	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    			throws BeanDefinitionStoreException;
    	void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    
    	
    	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    
    	...
    }
    
    

    就拿上面那几个方法举例:

    1. registerBeanDefinition,这个就是往名单上加人
    2. removeBeanDefinition,这个就是自己不去了
    3. BeanDefinition getBeanDefinition(String beanName),这个可以用自己名字查到报名信息,改个名字,没问题吧?

    @Configuration注解的处理,就是依赖于一个实现了BeanDefinitionRegistry接口的类,在这个类里,它干了很多事:

    假如现在的名单里,只有我们的启动类,对吧,上面标了一坨注解,什么各种Enable,什么Component-scan啦,都有。但我们标注这些,比如component-scan,不是摆着玩的,是要去对应的package下,帮我们扫描bean的;这就是相当于说,要往名单上加人。

    大致的逻辑可以理解为:

    1. 拿到初始名单,这里就是启动类的bean definition,以及一些其他手动弄进去的bean definition

    2. 通过实现了BeanDefinitionRegistryConfigurationClassPostProcessor,来看看第一步的初始名单中,有没有注解@Component-scan,如果没注解,直接返回;如果注解了,进入第三步;

    3. 拿到@component-scan里配置的要扫描的package名,然后获取这个package下的全部class,然后看看这些class满不满足条件(比如,只认注解了controller、service等注解的)

    4. 第三步筛出来的,满足条件的class,它们本身合格了,可以作为bean了;然后看看它们有没有作为配置类的资格,我拿下面的举例:

      @Component
      @ComponentScan(value = "xxxx.xxxx")
      public class PersonService {
          private String personname1;
      }
      

      本身,上面这个例子中,PersonService因为component-scan的功劳,已经被收为bean了,但是,这不是结束,因为它自己上面还注解了@ComponentScan注解,而这,就需要去递归处理。

      有些同学会觉得有点极端,maybe,但是,下面的例子极端吗:

      @Component
      @Import({MainClassForTestAnnotationConfig.class})
      public class PersonService {
          private String personname1;
      
      

      如果觉得@Import极端,那么@ImportResource去导入xml文件里的bean,这个场景,有些时候还是会遇到吧,比如,要兼容老程序的时候。

      而我要说的就是,在ConfigurationClassPostProcessor处理@configuration注解的过程中,如果发现这个类上有以下行为,都会递归处理:

      1. 内部类先解析
      2. PropertySource 注解
      3. ComponentScan注解
      4. Import 注解
      5. ImportResource注解
      6. Bean 注解的方法
      7. 处理superClass

      总体来说,这个类还是比较难的,而且会递归处理。

    我们今天的demo,为了聚焦,也为了实现简单,先只处理了@component-scan本身的递归。

    接下来,就来看看具体实现。

    具体实现

    测试类,主逻辑,驱动整体流程

    
    @MyConfiguration
    @MyComponentScan(value = "org.springframework.test")
    public class BootStrap {
        public static void main(String[] args) {
            // 1
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
            AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(BootStrap.class);
            context.registerBeanDefinition(BootStrap.class.getName(), beanDefinition);
    
            /**
             * 2 注册一个beanFactoryPostProcessor
             */
            RootBeanDefinition def = new RootBeanDefinition(MyConfigurationClassPostProcessor.class);
            def.setSource(null);
            context.registerBeanDefinition(MyConfigurationClassPostProcessor.class.getName(),
                    def);
    		// 3 
            context.refresh();
    		// 4
            PersonService bean = context.getBean(PersonService.class);
            System.out.println(bean);
            
            AnotherPersonService anotherPersonService = context.getBean(AnotherPersonService.class);
            System.out.println(anotherPersonService);
        }
    }
    
    • 1处,使用spring默认的注解驱动上下文,设置:config的起点类为当前类,注册到spring容器
    • 2处,注册一个 MyConfigurationClassPostProcessor 到spring 容器,这个和前面讲的ConfigurationClassPostProcessor 效果类似,用于解析我们自己的@MyComponentScan
    • 3处,加载上下文
    • 4处,获取bean,检测效果。

    MyConfigurationClassPostProcessor,解析@MyComponentScan

    @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            log.info("postProcessBeanDefinitionRegistry...");
            
            /**
             * 1: 找到标注了{@link org.springframework.custom.annotation.MyConfiguration}注解的类
             * 这些类就是我们的配置类
             * 我们通过这些类,可以发现更多的bean definition
             */
            Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<BeanDefinitionHolder>();
            for (String beanName : registry.getBeanDefinitionNames()) {
                BeanDefinition beanDef = registry.getBeanDefinition(beanName);
                if (MyConfigurationUtils.checkConfigurationClassCandidate(beanDef)) {
                    beanDefinitionHolders.add(new BeanDefinitionHolder(beanDef, beanName));
                }
            }
    		// 2
            if (CollectionUtils.isEmpty(beanDefinitionHolders)) {
                return;
            }
    		// 3
            MyConfigurationClassParser parser = new MyConfigurationClassParser(environment,registry);
            parser.parse(beanDefinitionHolders);
        }
    
    • 1处,找到目前的,标注了 MyConfiguration注解的全部bean definition
    • 2处,如果不存在,返回
    • 3处,对第一步找到的集合,进行下一步处理

    具体解析的工作,落在了第三步的身上,具体说,是MyConfigurationClassParser类的身上。

    MyConfigurationClassParser具体执行者

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
            for (BeanDefinitionHolder holder : configCandidates) {
                BeanDefinition bd = holder.getBeanDefinition();
                String className = bd.getBeanClassName();
    
                try {
                    processConfigurationClass(className);
                }
                catch (IOException ex) {
                    throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
                }
            }
        }
    

    这里就是对每个找到的配置类进行处理。比如,我们这里的demo,找到的就是启动类。

    然后调用了下面的方法:

    
        protected void processConfigurationClass(String className) throws IOException {
            MetadataReader metadataReader = MyConfigurationUtils.getMetadataReader(className);
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
    
            /**
             * 1. 判断该类上,是否有标注{@link MyComponentScan}
             */
            Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(MyComponentScan.class.getName(), true);
            AnnotationAttributes componentScan = AnnotationAttributes.fromMap(annotationAttributes);
    
            /**
             * 2. 如果类上有这个{@link MyComponentScan},则需要进行处理
             */
            if (componentScan != null) {
                /**
                 * 3. 马上扫描这个base package路径下的bean,在里面,会注册beanDefinition到bean registry
                 */
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, annotationMetadata.getClassName());
    
                /**
                 * 4. 如果扫描回来的bean definition不为空,递归处理
                 */
                if (!CollectionUtils.isEmpty(scannedBeanDefinitions)) {
                    this.parse(scannedBeanDefinitions);
                }
            }
    
        }
    
    • 1处,判断该类上,是否有标注 @MyComponentScan
    • 2处,如果类上有这个 @MyComponentScan ,则需要进行处理
    • 3处,马上扫描这个base package路径下的bean,在里面,会注册beanDefinition到bean registry
    • 4处,对第三步扫描,得到bean,递归处理,查找更多bean

    重点是这里的第三步,交给了一个叫componentScanParser的去处理,这个componentScanParser是在本类初始化的时候赋值的:

        public MyConfigurationClassParser(Environment environment, BeanDefinitionRegistry registry) 	{
            this.environment = environment;
            this.registry = registry;
            this.componentScanParser = new MyComponentScanParser(componentScanBeanNameGenerator,
                    environment,registry);
        }
    

    MyComponentScanParser的处理过程

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String className) {
            // 1
            String basePackage = componentScan.getString("value");
            // 2
            includeFilters.add(new AnnotationTypeFilter(MyComponent.class));
            // 3
            Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    
            /**
             * 4 获取包下的全部bean definition
             */
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            /**
             * 5 对扫描回来的bean,进行一定的处理,然后注册到bean registry
             */
            for (BeanDefinition candidate : candidates) {
                String generateBeanName = componentScanBeanNameGenerator.generateBeanName(candidate, registry);
    
                if (candidate instanceof AbstractBeanDefinition) {
                    ((AbstractBeanDefinition)candidate).applyDefaults(this.beanDefinitionDefaults);
                }
    
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
    
                boolean b = checkCandidate(generateBeanName, candidate);
                if (b) {
                    // 6
                    beanDefinitions.add(new BeanDefinitionHolder(candidate,generateBeanName));
                }
            }
    
            /**
             * 7 注册到bean definition registry
             */
            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {
                registry.registerBeanDefinition(beanDefinitionHolder.getBeanName(),beanDefinitionHolder.getBeanDefinition());
            }
    
            return beanDefinitions;
        }
    
    • 1处,获取MyComponentScan注解中value信息,表示要扫描的package
    • 2处,设置识别bean的规则,这里是把注解了@MyComponent的,认为是自己人
    • 3处,定义变量,用于存放返回的结果
    • 4处,扫描包下的全部满足条件的,bean definition
    • 5处,处理第4步拿到的bean definition集合
    • 6处,加到待返回的结果集
    • 7处,注册到spring容器

    以上,只有第4处需要再次说明,其他都比较简单。

    获取满足条件的bean definition的过程

    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
        try {
            // 1
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + "/" + this.resourcePattern;
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            // 2
            for (Resource resource : resources) {
                log.info("Scanning " + resource);
    
                if (!resource.isReadable()) {
                    continue;
                }
    			// 3
                MetadataReader metadataReader = MyConfigurationUtils.getMetadataReader(resource);
                // 4
                if (isCandidateComponent(metadataReader)) {
                    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                    sbd.setResource(resource);
                    sbd.setSource(resource);
                    // 5
                    candidates.add(sbd);
                }
                else {
                    log.info("Ignored because not matching any filter: " + resource);
                }
            }
        }
        ...
    
        return candidates;
    }
    
    • 1处,扫描包下的全部class
    • 2处,遍历class
    • 3处,获取该class的注解信息
    • 4处,利用注解信息,判断是否是自己人(注解了@MyComponent)
    • 5处,自己人,准备带走

    其中,第4处,实现如下:

        
    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
            for (TypeFilter tf : this.includeFilters) {
                if (tf.match(metadataReader, MyConfigurationUtils.getMetadataReaderFactory())) {
                    return true;
                }
            }
            return false;
        }
    

    就是用includeFilters去匹配,大家还记得前面,我们设置了吧:

    includeFilters.add(new AnnotationTypeFilter(MyComponent.class));
    

    大致的过程,就是这样了。

    dubbo中的实现,粗浅分析

    我发现,好像和我上面说的,差得不太多,我也没用过dubbo,确实没参考dubbo的实现。

    比如,它也定义了一个BeanDefinitionRegistryPostProcessor的实现类,叫:

    public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
            ResourceLoaderAware, BeanClassLoaderAware {
    

    其实现如下:

    @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
            registerBeans(registry, DubboBootstrapApplicationListener.class);
    		// 1
            Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
    
            if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
                // 2
                registerServiceBeans(resolvedPackagesToScan, registry);
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
                }
            }
    
        }
    
    • 1处,找到要扫描的package
    • 2处,扫描指定包

    上面的2处,实现如下:

    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
    
            DubboClassPathBeanDefinitionScanner scanner =
                    new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
    		
            BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
    
            scanner.setBeanNameGenerator(beanNameGenerator);
    		// 1
            scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
    		// 2
            scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));
    		// 3
            for (String packageToScan : packagesToScan) {
    
                // 4 Registers @Service Bean first
                scanner.scan(packageToScan);
    			// 5
                Set<BeanDefinitionHolder> beanDefinitionHolders =
                        findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
    			// 6
                if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
    
                    for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                        // 7
                        registerServiceBean(beanDefinitionHolder, registry, scanner);
                    }
    
                }
    
            }
    
        }
    
    • 1,设置includeFilters,注解类型,注解了org.apache.dubbo.config.annotation.Service类型就算
    • 2,还是设置includeFilters,只是为了兼容以前的
    • 3,遍历要扫描的package
    • 4,扫描指定的包
    • 5,扫描包,获取到满足条件的集合
    • 6,第五步返回不为空,则开始在下面的第7步去注册到spring
    • 7,注册到spring

    总结

    经过前面的讲解,大家应该立即更清楚一些了吧,如果还是有点懵,那最好把demo拉下来试试。

    https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-annotation-custom-component-scan

    如果大家觉得还有点帮助,帮忙点个赞吧。

  • 相关阅读:
    dnsServer SmartDNS / smartdns / DNS UDP 53
    springBoot 自定义注解 + 自定义异常捕获实战
    查询出ES库所有数据
    springBoot2.X 支持http、https访问
    配置ES IK分词器自定义字典
    搭建angular开发环境 运行 ng-alain框架
    【jQuery】 选择器
    【jQuery】 js 对象
    【C#】 URL Protocol
    【C#】 反射
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/12632419.html
Copyright © 2020-2023  润新知