Spring 的類掃描器分析 - ClassPathBeanDefinitionScanner

1. 簡(jiǎn)介

SpringBoot項(xiàng)目中或者 Spring項(xiàng)目中配置<context:component-scan base-package="com.example.demo" />
,那么在IOC 容器初始化階段(調(diào)用beanFactoryPostProcessor階段) 就會(huì)采用ClassPathBeanDefinitionScanner進(jìn)行掃描包下 所有類,并將符合過濾條件的類注冊(cè)到IOC 容器內(nèi)。Mybatis 的Mapper注冊(cè)器(ClassPathMapperScanner) 是同過繼承ClassPathBeanDefinitionScanner,并且自定義了過濾器規(guī)則來實(shí)現(xiàn)的。具體的 調(diào)用過程并不會(huì)在這里說明,只是想在這里描述ClassPathBeanDefinitionScanner是如何 掃描 和 注冊(cè)BeanDefinition的。

2. 作用

ClassPathBeanDefinitionScanner作用就是將指定包下的類通過一定規(guī)則過濾后 將Class 信息包裝成 BeanDefinition 的形式注冊(cè)到IOC容器中。

  1. 根據(jù)指定掃描報(bào)名 生成匹配規(guī)則。
     例如:classpath*:com.example.demo/**/*.class
  1. resourcePatternResolver(資源加載器)根據(jù)匹配規(guī)則 獲取 Resource[] 。
    • Resource數(shù)組中每一個(gè) 對(duì)象 都是對(duì)應(yīng)一個(gè) Class 文件,Spring 用Resource定位資源, 封裝了資源的IO操作。
    • 這里的 Resource 實(shí)際類型是 FileSystemResource.
    • 資源加載器 其實(shí)就是 容器 本身。
  2. meteDataFactory根據(jù) Resouce 獲取到 MetadataReader 對(duì)象
    • MetadataReader 提供了 獲取 一個(gè)Class 文件的 ClassMetadata 和 AnnotationMetadata 的 操作。
  3. 根據(jù)過濾器規(guī)則 匹配 MetadataReader中的類 進(jìn)行過濾,比如 是否是Componet 注解標(biāo)注的類。
  4. 轉(zhuǎn)換 MetadataReader 為 BeanDefinition.
  5. 將BeanDefinition 注冊(cè)到 BeanFactory.

3. 默認(rèn)的過濾器注冊(cè)

過濾器用來過濾 從指定包下面查找到的 Class ,如果能通過過濾器,那么這個(gè)class 就會(huì)被轉(zhuǎn)換成BeanDefinition 注冊(cè)到容器。

如果在實(shí)例化ClassPathBeanDefinitionScanner時(shí),沒有說明要使用用戶自定義的過濾器的話,那么就會(huì)采用下面的默認(rèn)的過濾器規(guī)則。

注冊(cè)了@Component 過濾器到 includeFiters ,相當(dāng)于 同時(shí)注冊(cè)了所有被@Component注釋的注解,包括@Service ,@Repository,@Controller,同時(shí)也支持java EE6 的javax.annotation.ManagedBean 和 JSR-330 的 @Named 注解。

protected void registerDefaultFilters() {
    // 添加Component 注解過濾器
    //這就是為什么 @Service @Controller @Repostory @Component 能夠起作用的原因。
        this.includeFilters.add(new AnnotationTypeFilter(Component.class));
        ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
        try {
            // 添加ManagedBean 注解過濾器
            this.includeFilters.add(new AnnotationTypeFilter(
                    ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
            logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
        }
        catch (ClassNotFoundException ex) {
            // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
        }
        try {
            // 添加Named 注解過濾器
            this.includeFilters.add(new AnnotationTypeFilter(
                    ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
            logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
        }
        catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
    }

4. 執(zhí)行掃描(doScan)

實(shí)際執(zhí)行包掃描,進(jìn)行封裝的函數(shù)是findCandidateComponents,findCandidateComponents定義在父類中。ClassPathBeanDefinitionScanner的主要功能實(shí)現(xiàn)都在這個(gè)函數(shù)中。

doScan流程.png
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
        try {
            // 1.根據(jù)指定包名 生成包搜索路徑
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            //2. 資源加載器 加載搜索路徑下的 所有class 轉(zhuǎn)換為 Resource[]
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            // 3. 循環(huán) 處理每一個(gè) resource 
            for (Resource resource : resources) {
            
                if (resource.isReadable()) {
                    try {
                        // 讀取類的 注解信息 和 類信息 ,信息儲(chǔ)存到  MetadataReader
                        // 
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                     // 執(zhí)行判斷是否符合 過濾器規(guī)則,函數(shù)內(nèi)部用過濾器 對(duì)metadataReader 過濾  
                        if (isCandidateComponent(metadataReader)) {
                            //把符合條件的 類轉(zhuǎn)換成 BeanDefinition
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
                            // 再次判斷 如果是實(shí)體類 返回true,如果是抽象類,但是抽象方法 被 @Lookup 注解注釋返回true 
                            if (isCandidateComponent(sbd)) {
                                if (debugEnabled) {
                                    logger.debug("Identified candidate component class: " + resource);
                                }
                                candidates.add(sbd);
                            }
                //省略了 部分代碼
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return candidates;
    }

5. 自定義掃描器

通過自定義的掃描器,掃描指定包下所有被@MyBean 注釋的類。

5.1 定義一個(gè)注解,并注釋一個(gè)類
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyBean {

}

@MyBean
public class TestScannerBean {

}

5.2 編寫掃描器
class MyClassPathDefinitonScanner extends ClassPathBeanDefinitionScanner{
        private Class type;
       public MyClassPathDefinitonScanner(BeanDefinitionRegistry registry,Class<? extends Annotation> type){
            super(registry,false);
            this.type = type;
        }
        /**
         * 注冊(cè) 過濾器
         */
        public void registerTypeFilter(){
           addIncludeFilter(new AnnotationTypeFilter(type));
        }
    }
5.3 測(cè)試自定義掃描器
  • 測(cè)試代碼
 @Test
    public void testSimpleScan() {
        String BASE_PACKAGE = "com.example.demo";
        GenericApplicationContext context = new GenericApplicationContext();
        MyClassPathDefinitonScanner myClassPathDefinitonScanner = new MyClassPathDefinitonScanner(context, MyBean.class);
// 注冊(cè)過濾器
        myClassPathDefinitonScanner.registerTypeFilter();
        int beanCount = myClassPathDefinitonScanner.scan(BASE_PACKAGE);
        context.refresh();
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        System.out.println(beanCount);
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }

  • 測(cè)試結(jié)果
7
//這個(gè)就是我們掃描到的bean 
testScannerBean
//下面這些 是 父類掃描器 注冊(cè)的 beanFactory后置處理器 
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

6. 總結(jié)

通過對(duì)ClassPathBeanDefinitionScanner的分析,終于揭開了Spring 的類掃描的神秘面紗,其實(shí),就是對(duì)指定路徑下的 所有class 文件進(jìn)行逐一排查,對(duì)符合條件的 class ,封裝成 BeanDefinition注冊(cè)到IOC 容器。

理解ClassPathBeanDefinitionScanner的工作原理,可以幫助理解Spring IOC 容器的初始化過程。

同時(shí)對(duì)理解MyBatis 的 Mapper 掃描 也是有很大的幫助。
因?yàn)?MyBatis 的MapperScannerConfigurer的底層實(shí)現(xiàn)也是一個(gè)ClassPathBeanDefinitionScanner的子類。就像我們自定義掃描器那樣,自定定義了 過濾器的過濾規(guī)則。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.1 spring IoC容器和beans的簡(jiǎn)介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器,...
    simoscode閱讀 6,844評(píng)論 2 22
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 1.1 Spring IoC容器和bean簡(jiǎn)介 本章介紹了Spring Framework實(shí)現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,665評(píng)論 0 8
  • 本來是準(zhǔn)備看一看Spring源碼的。然后在知乎上看到來一個(gè)帖子,說有一群**自己連Spring官方文檔都沒有完全讀...
    此魚不得水閱讀 7,036評(píng)論 4 21
  • 晚上下班回到家里,突然接到爸爸的電話。 “我現(xiàn)在在城里晚上八點(diǎn)多回家睡覺啊?” 我有幾分詫異,下午的時(shí)候家里開始送...
    林雅閱讀 909評(píng)論 1 2

友情鏈接更多精彩內(nèi)容