Spring 的類掃描器分析 - ClassPathBeanDefinitionScanner

1. 簡介

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

2. 作用

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

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

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

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

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

注冊了@Component 過濾器到 includeFiters ,相當(dāng)于 同時(shí)注冊了所有被@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 {
                        // 讀取類的 注解信息 和 類信息 强胰,信息儲存到  MetadataReader
                        // 
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                     // 執(zhí)行判斷是否符合 過濾器規(guī)則舱沧,函數(shù)內(nèi)部用過濾器 對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;
        }
        /**
         * 注冊 過濾器
         */
        public void registerTypeFilter(){
           addIncludeFilter(new AnnotationTypeFilter(type));
        }
    }
5.3 測試自定義掃描器
  • 測試代碼
 @Test
    public void testSimpleScan() {
        String BASE_PACKAGE = "com.example.demo";
        GenericApplicationContext context = new GenericApplicationContext();
        MyClassPathDefinitonScanner myClassPathDefinitonScanner = new MyClassPathDefinitonScanner(context, MyBean.class);
// 注冊過濾器
        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);
        }
    }

  • 測試結(jié)果
7
//這個(gè)就是我們掃描到的bean 
testScannerBean
//下面這些 是 父類掃描器 注冊的 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é)

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末典阵,一起剝皮案震驚了整個(gè)濱河市奋渔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壮啊,老刑警劉巖嫉鲸,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歹啼,居然都是意外死亡玄渗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門狸眼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來藤树,“玉大人,你說我怎么就攤上這事拓萌∷甑觯” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長屡限。 經(jīng)常有香客問我品嚣,道長,這世上最難降的妖魔是什么钧大? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任翰撑,我火速辦了婚禮,結(jié)果婚禮上拓型,老公的妹妹穿的比我還像新娘额嘿。我一直安慰自己,他們只是感情好劣挫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布册养。 她就那樣靜靜地躺著,像睡著了一般压固。 火紅的嫁衣襯著肌膚如雪球拦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天帐我,我揣著相機(jī)與錄音坎炼,去河邊找鬼。 笑死拦键,一個(gè)胖子當(dāng)著我的面吹牛谣光,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芬为,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼萄金,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了媚朦?” 一聲冷哼從身側(cè)響起氧敢,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎询张,沒想到半個(gè)月后孙乖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡份氧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年唯袄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜗帜。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡越妈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钮糖,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布店归,位于F島的核電站阎抒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏消痛。R本人自食惡果不足惜且叁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秩伞。 院中可真熱鬧逞带,春花似錦、人聲如沸纱新。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脸爱。三九已至遇汞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間簿废,已是汗流浹背空入。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留族檬,地道東北人歪赢。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像单料,于是被迫代替她去往敵國和親埋凯。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

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