1. 程式人生 > >曹工說Spring Boot原始碼(28)-- Spring的component-scan機制,讓你自己來進行簡單實現,怎麼辦

曹工說Spring Boot原始碼(28)-- Spring的component-scan機制,讓你自己來進行簡單實現,怎麼辦

# 寫在前面的話 相關背景及資源: [曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享](https://www.cnblogs.com/grey-wolf/p/12044199.html) [曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解](https://www.cnblogs.com/grey-wolf/p/12051957.html ) [曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下](https://www.cnblogs.com/grey-wolf/p/12070377.html) [曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?](https://www.cnblogs.com/grey-wolf/p/12078673.html) [曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean](https://www.cnblogs.com/grey-wolf/p/12093929.html) [曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的](https://www.cnblogs.com/grey-wolf/p/12114604.html ) [曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)](https://www.cnblogs.com/grey-wolf/p/12151809.html) [曹工說Spring Boot原始碼(8)-- Spring解析xml檔案,到底從中得到了什麼(util名稱空間)](https://www.cnblogs.com/grey-wolf/p/12158935.html) [曹工說Spring Boot原始碼(9)-- Spring解析xml檔案,到底從中得到了什麼(context名稱空間上)](https://www.cnblogs.com/grey-wolf/p/12189842.html) [曹工說Spring Boot原始碼(10)-- Spring解析xml檔案,到底從中得到了什麼(context:annotation-config 解析)](https://www.cnblogs.com/grey-wolf/p/12199334.html) [曹工說Spring Boot原始碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)](https://www.cnblogs.com/grey-wolf/p/12203743.html) [曹工說Spring Boot原始碼(12)-- Spring解析xml檔案,到底從中得到了什麼(context:component-scan完整解析)](https://www.cnblogs.com/grey-wolf/p/12214408.html) [曹工說Spring Boot原始碼(13)-- AspectJ的執行時織入(Load-Time-Weaving),基本內容是講清楚了(附原始碼)](https://www.cnblogs.com/grey-wolf/p/12228958.html) [曹工說Spring Boot原始碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎麼和Spring Instrumentation整合](https://www.cnblogs.com/grey-wolf/p/12283544.html) [曹工說Spring Boot原始碼(15)-- Spring從xml檔案裡到底得到了什麼(context:load-time-weaver 完整解析)](https://www.cnblogs.com/grey-wolf/p/12288391.html) [曹工說Spring Boot原始碼(16)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【上】)](https://www.cnblogs.com/grey-wolf/p/12314954.html) [曹工說Spring Boot原始碼(17)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【中】)](https://www.cnblogs.com/grey-wolf/p/12317612.html) [曹工說Spring Boot原始碼(18)-- Spring AOP原始碼分析三部曲,終於快講完了 (aop:config完整解析【下】)](https://www.cnblogs.com/grey-wolf/p/12322587.html) [曹工說Spring Boot原始碼(19)-- Spring 帶給我們的工具利器,建立代理不用愁(ProxyFactory)](https://www.cnblogs.com/grey-wolf/p/12359963.html) [曹工說Spring Boot原始碼(20)-- 碼網恢恢,疏而不漏,如何記錄Spring RedisTemplate每次操作日誌](https://www.cnblogs.com/grey-wolf/p/12375656.html) [曹工說Spring Boot原始碼(21)-- 為了讓大家理解Spring Aop利器ProxyFactory,我已經拼了](https://www.cnblogs.com/grey-wolf/p/12384356.html) [曹工說Spring Boot原始碼(22)-- 你說我Spring Aop依賴AspectJ,我依賴它什麼了](https://www.cnblogs.com/grey-wolf/p/12418425.html) [曹工說Spring Boot原始碼(23)-- ASM又立功了,Spring原來是這麼遞迴獲取註解的元註解的](https://www.cnblogs.com/grey-wolf/p/12535152.html) [曹工說Spring Boot原始碼(24)-- Spring註解掃描的瑞士軍刀,asm技術實戰(上)](https://www.cnblogs.com/grey-wolf/p/12571217.html) [曹工說Spring Boot原始碼(25)-- Spring註解掃描的瑞士軍刀,ASM + Java Instrumentation,順便提提Jar包破解](https://www.cnblogs.com/grey-wolf/p/12584861.html) [曹工說Spring Boot原始碼(26)-- 學習位元組碼也太難了,實在不能忍受了,寫了個小小的位元組碼執行引擎](https://www.cnblogs.com/grey-wolf/p/12600097.html) [曹工說Spring Boot原始碼(27)-- Spring的component-scan,光是include-filter屬性的各種配置方式,就夠玩半天了](https://www.cnblogs.com/grey-wolf/p/12601823.html) [工程程式碼地址](https://gitee.com/ckl111/spring-boot-first-version-learn ) [思維導圖地址](https://www.processon.com/view/link/5deeefdee4b0e2c298aa5596) 工程結構圖: ![](https://img2018.cnblogs.com/blog/519126/201912/519126-20191215144930717-1919774390.png) # 概要 本講相對獨立,我們也不愛說廢話,直接說本講要做啥。 大家知道,@Component-scan註解,在註解時代,最主要的用法就是指定一個package的名稱,然後spring就會去對應的包下面,掃描註解了@Controller、@Service、@Repository等註解的類,然後註冊為bean。 spring boot時代,這個註解則站到了幕後,如下: ```java @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類 ```java @MyConfiguration @MyComponentScan(value = "org.springframework.test") public class BootStrap { public static void main(String[] args) { ... } ``` 這個上面定義了2個自定義的註解,都加了個字首"My"。下面簡單看看。 ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyConfiguration { } ``` 這個的效果,類似於@configuration,表示我們是一個配置類,配置類上一般會有一堆其他註解,來引入其他bean definition。 然後是MyComponentScan 註解: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyComponentScan { /* * 要掃描的包名 */ String value(); } ``` 2. 我們的target包下,有一個需要我們掃描的bean,如下: ```java 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. 最終的測試效果如下: ```java 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呢,大家看看: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { } ``` 該註解,是註解了@configuration。 其實就是手動指定一個配置類,這個配置類,原始碼裡好像是叫做startUp config,作為一個起點,通過這個起點,我們能發現在這個起點類上的,更多的元註解,比如,在我們公司的真實專案中,啟動類就配置了一堆東西: ```java @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`。 這個類呢,實現瞭如下介面: ```java 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,但我另一個同事沒啥事,他可以去 而要實現這些,只要你實現前面我們提到的那個介面的方法即可,大家可以再觀察下這個方法: ```java void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; ``` 入參是registry,也就是當前的報名表,報名表都給你了,你還有啥不能幹的? ```java 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. 通過實現了`BeanDefinitionRegistry`的`ConfigurationClassPostProcessor`,來看看第一步的初始名單中,有沒有註解@Component-scan,如果沒註解,直接返回;如果註解了,進入第三步; 3. 拿到@component-scan裡配置的要掃描的package名,然後獲取這個package下的全部class,然後看看這些class滿不滿足條件(比如,只認註解了controller、service等註解的) 4. 第三步篩出來的,滿足條件的class,它們本身合格了,可以作為bean了;然後看看它們有沒有作為配置類的資格,我拿下面的舉例: ```java @Component @ComponentScan(value = "xxxx.xxxx") public class PersonService { private String personname1; } ``` 本身,上面這個例子中,PersonService因為component-scan的功勞,已經被收為bean了,但是,這不是結束,因為它自己上面還註解了@ComponentScan註解,而這,就需要去遞迴處理。 有些同學會覺得有點極端,maybe,但是,下面的例子極端嗎: ```java @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本身的遞迴。 接下來,就來看看具體實現。 # 具體實現 ## 測試類,主邏輯,驅動整體流程 ```java @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 ```java @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { log.info("postProcessBeanDefinitionRegistry..."); /** * 1: 找到標註了{@link org.springframework.custom.annotation.MyConfiguration}註解的類 * 這些類就是我們的配置類 * 我們通過這些類,可以發現更多的bean definition */ Set beanDefinitionHolders = new LinkedHashSet(); 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具體執行者 ```java public void parse(Set 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,找到的就是啟動類。 然後呼叫了下面的方法: ```java protected void processConfigurationClass(String className) throws IOException { MetadataReader metadataReader = MyConfigurationUtils.getMetadataReader(className); AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); /** * 1. 判斷該類上,是否有標註{@link MyComponentScan} */ Map 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 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是在本類初始化的時候賦值的: ```java public MyConfigurationClassParser(Environment environment, BeanDefinitionRegistry registry) { this.environment = environment; this.registry = registry; this.componentScanParser = new MyComponentScanParser(componentScanBeanNameGenerator, environment,registry); } ``` ## MyComponentScanParser的處理過程 ```java public Set parse(AnnotationAttributes componentScan, String className) { // 1 String basePackage = componentScan.getString("value"); // 2 includeFilters.add(new AnnotationTypeFilter(MyComponent.class)); // 3 Set beanDefinitions = new LinkedHashSet(); /** * 4 獲取包下的全部bean definition */ Set 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的過程 ```java public Set findCandidateComponents(String basePackage) { Set candidates = new LinkedHashSet(); 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處,實現如下: ```java protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, MyConfigurationUtils.getMetadataReaderFactory())) { return true; } } return false; } ``` 就是用includeFilters去匹配,大家還記得前面,我們設定了吧: ```java includeFilters.add(new AnnotationTypeFilter(MyComponent.class)); ``` 大致的過程,就是這樣了。 # dubbo中的實現,粗淺分析 我發現,好像和我上面說的,差得不太多,我也沒用過dubbo,確實沒參考dubbo的實現。 比如,它也定義了一個BeanDefinitionRegistryPostProcessor的實現類,叫: ```java public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware { ``` 其實現如下: ```java @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { registerBeans(registry, DubboBootstrapApplicationListener.class); // 1 Set 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處,實現如下: ```java private void registerServiceBeans(Set 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 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拉下來試試。
如果大家覺得還有點幫助,幫忙點個