曹工說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

如果大家覺得還有點幫助,幫忙點個贊吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM