spring容器加載分析 三Configuration類解析

Spring中對Configuration類的解析是通過ConfigurationClassPostProcessor進(jìn)行的掉缺,這個類是BeanFactoryPostProcessor的實(shí)現(xiàn)翼抠,在容器刷新方法中invokeBeanFactoryPostProcessors(beanFactory)這個方法調(diào)用所有的BeanFactoryPostProcessor店煞,同時(shí)也就啟動了Configuration類解析的苞慢。
解析的總體過程:

1诵原、從Bean工廠找出所有Configuratio類加入configCandidates列表中,所謂Configuratio類就是被@Configuration注解的類或者包含@Bean、@Component绍赛、@ComponentScan蔓纠、@Import、@ImportResource注解的類吗蚌。
2腿倚、根據(jù)@Order對configCandidates列表進(jìn)行排序
3、遍歷configCandidates蚯妇,使用委托類ConfigurationClassParser解析配置項(xiàng)敷燎,包含@PropertySources注解解析、@ComponentScan注解解析箩言、@Import注解解析硬贯、@Bean注解解析。
4陨收、遍歷configCandidates饭豹,使用委托類ConfigurationClassBeanDefinitionReader注冊解析好的BeanDefinition

具體配置項(xiàng)解析過程在ConfigurationClassParser類中實(shí)現(xiàn):

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {
    // Recursively process any member (nested) classes first
    processMemberClasses(configClass, sourceClass);
    // 1、處理@PropertySources注解,解析屬性文件
    // 將解析出來的屬性資源添加到environment
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), PropertySources.class,
            org.springframework.context.annotation.PropertySource.class)) {
        if (this.environment instanceof ConfigurableEnvironment) {
            processPropertySource(propertySource);
        }
        else {
            logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                    "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }
    // 2务漩、處理@ComponentScan注解拄衰,通過ComponentScanAnnotationParser解析@ComponentScan注解
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        
        for (AnnotationAttributes componentScan : componentScans) {
            
            // The config class is annotated with @ComponentScan -> perform the scan immediately
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // Check the set of scanned definitions for any further config classes and parse recursively if needed
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                // 檢查是否是ConfigurationClass,如果是走遞歸
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
                    parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }
    //3饵骨、處理@Import注解
    processImports(configClass, sourceClass, getImports(sourceClass), true);
    // Process any @ImportResource annotations
    // 處理@ImportResource注解:獲取@ImportResource注解的locations屬性翘悉,得到資源文件的地址信息。
    // 然后遍歷這些資源文件并把它們添加到配置類的importedResources屬性中
    if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
        AnnotationAttributes importResource =
                AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        String[] resources = importResource.getStringArray("locations");
        Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
        for (String resource : resources) {
            String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
            configClass.addImportedResource(resolvedResource, readerClass);
        }
    }
    // 4宏悦、處理@Bean注解:獲取被@Bean注解修飾的方法镐确,然后添加到配置類的beanMethods屬性中
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }
    // 處理接口和父類
    // Process default methods on interfaces
    processInterfaces(configClass, sourceClass);
    // Process superclass, if any
    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            // Superclass found, return its annotation metadata and recurse
            return sourceClass.getSuperClass();
        }
    }
    // No superclass -> processing is complete
    return null;
}

step 1 處理@PropertySources注解
解析屬性文件,將解析出來的屬性資源添加到Environment中
step 2 處理@ComponentScan注解
通過ComponentScanAnnotationParser解析@ComponentScan注解饼煞,解析方法為parse:
2.1)源葫、實(shí)例化元數(shù)據(jù)(注解)掃描器ClassPathBeanDefinitionScanner
2.2)、分析出Bean名稱生成器BeanNameGenerator
2.3)砖瞧、分析出代理模型ScopedProxyMode
2.4)息堂、分析出resourcePattern,默認(rèn)值"*/.class"
2.5)块促、分析出掃描包含的目錄includeFilters荣堰、排除的目錄excludeFilters,生成過濾規(guī)則
2.6)竭翠、分析出加載類型振坚,延遲或非延遲
2.7)、將上述屬性設(shè)置到ClassPathBeanDefinitionScanner
2.8)斋扰、分析出掃描的包路徑數(shù)組basePackages渡八,
2.9)啃洋、使用ClassPathBeanDefinitionScanner掃描basePackages包中符合條件的Bean注冊到容器,然后檢查Bean是否為ConfigurationClass屎鳍,如果是則遞歸解析宏娄。掃描過程:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    //創(chuàng)建一個集合,存放掃描到BeanDefinition
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
     //遍歷掃描所有給定的包  
    for (String basePackage : basePackages) {
        //調(diào)用父類ClassPathScanningCandidateComponentProvider的方法掃描給定類路徑逮壁,獲取符合條件的BeanDefinition
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            // 普通的BeanDefinition
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            // 注解的BeanDefinition
            // 處理注解@Primary孵坚、@DependsOn等Bean注解
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            // 檢查候選的,主要是檢查BeanFactory中是否包含此BeanDefinition
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

其中findCandidateComponents(basePackage)是調(diào)用父類ClassPathScanningCandidateComponentProvider中的方法來獲取符合條件的BeanDefinition窥淆。這個類在初始化的時(shí)候卖宠,會注冊一些默認(rèn)的過濾規(guī)則,與includeFilters和excludeFilters協(xié)調(diào)工作來過濾候選BeanDefinition祖乳,注冊Spring默認(rèn)規(guī)則:

protected void registerDefaultFilters() {
    //  向include過濾規(guī)則中添加@Component注解
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        // 向include過濾規(guī)則添加JSR-250:@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 {
        // 向include過濾規(guī)則添加JSR-330:@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.
    }
}

可以看到使用使用Spring @Component注解類逗堵、使用JSR-250:@ManagedBean、JSR-330:@Named注解的類都在include規(guī)則中眷昆,另外Spring中@Repository 蜒秤、@Service、@Controller亚斋、@Configuration都是被@Component注解過的組合注解作媚,所以添加了這些注解的類都會作為候選的Bean。獲取候選Bean:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    // 創(chuàng)建存儲掃描到的類的集合  
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
    try {
        // 默認(rèn)的包路徑:this.resourcePattern=” **/*.class”帅刊,  
        // resolveBasePackage方法將包名中的”.”轉(zhuǎn)換為文件系統(tǒng)的”/”  
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        // 循環(huán)獲取到的資源文件
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    //為指定資源獲取元數(shù)據(jù)讀取器纸泡,元信息讀取器通過匯編(ASM)讀//取資源元信息
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    //如果掃描到的類符合容器配置的過濾規(guī)則  
                    if (isCandidateComponent(metadataReader)) {
                        //通過匯編(ASM)讀取資源字節(jié)碼中的Bean定義元信息
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        //設(shè)置Bean定義來源于resource 
                        sbd.setResource(resource);
                         //為元數(shù)據(jù)元素設(shè)置配置資源對象  
                        sbd.setSource(resource);
                        //檢查Bean是否是一個可實(shí)例化的對象
                        if (isCandidateComponent(sbd)) {
                            if (debugEnabled) {
                                logger.debug("Identified candidate component class: " + resource);
                            }
                            candidates.add(sbd);
                        }
        // 省略 debug和異常處理的代碼 ... ...
    return candidates;
}

2.10)、掃描完成返回beanDefinitions赖瞒,如果發(fā)現(xiàn)beanDefinitions中有Configuration類女揭,進(jìn)行遞歸随珠。

step 3 處理@Import注解
通過getImports(sourceClass)獲取Configuration類中使用Import注解的類

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    Set<SourceClass> imports = new LinkedHashSet<SourceClass>();
    Set<SourceClass> visited = new LinkedHashSet<SourceClass>();
    collectImports(sourceClass, imports, visited);
    return imports;
}

private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
        throws IOException {
    if (visited.add(sourceClass)) {
        for (SourceClass annotation : sourceClass.getAnnotations()) {
            String annName = annotation.getMetadata().getClassName();
            if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
                collectImports(annotation, imports, visited);
            }
        }
        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    }
}

collectImports方法中包含一個遞歸驼修,如果你使用過Springboot可以會對里面的組合注解的解析有過疑問畅哑,其實(shí)springboot中組合注解的解析過程就是這個遞歸的過程:先遞歸找出所有的注解豌熄,然后再過濾出只有@Import注解的類,得到@Import注解的值墩莫。比如查找@SpringBootApplication注解的@Import注解數(shù)據(jù)的話津畸,首先發(fā)現(xiàn)@SpringBootApplication不是一個@Import注解蠢护,然后遞歸調(diào)用修飾了@SpringBootApplication的注解伺通,發(fā)現(xiàn)有個@EnableAutoConfiguration注解箍土,再次遞歸發(fā)現(xiàn)被@Import(EnableAutoConfigurationImportSelector.class)修飾,還有@AutoConfigurationPackage注解修飾罐监,再次遞歸@AutoConfigurationPackage注解吴藻,發(fā)現(xiàn)被@Import(AutoConfigurationPackages.Registrar.class)注解修飾,所以@SpringBootApplication注解對應(yīng)的@Import注解有2個弓柱,分別是@Import(AutoConfigurationPackages.Registrar.class)和@Import(EnableAutoConfigurationImportSelector.class)沟堡。所以遞歸的目的就是找出所有的Import類疮鲫,拿到這個Import列表進(jìn)行解析過程:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
        Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
    if (importCandidates.isEmpty()) {
        return;
    }
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        this.importStack.push(configClass);
        try {
            // 遍歷這些@Import注解內(nèi)部的屬性類集合ComponentScanAnnotationParser
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) {//如果這個類是個ImportSelector接口的實(shí)現(xiàn)類
                    // Candidate class is an ImportSelector -> delegate to it to determine imports
                    Class<?> candidateClass = candidate.loadClass();
                    // 實(shí)例化這個ImportSelector
                    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            selector, this.environment, this.resourceLoader, this.registry);
                    // 如果這個類也是DeferredImportSelector接口的實(shí)現(xiàn)類,
                    // 那么加入ConfigurationClassParser的deferredImportSelectors
                    if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectors.add(
                                new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                    }else {
                        // 否則調(diào)用ImportSelector的selectImports方法得到需要Import的類
                        // 然后對這些類遞歸做@Import注解的處理
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                        processImports(configClass, currentSourceClass, importSourceClasses, false);
                    }
                }
                // 如果這個類是ImportBeanDefinitionRegistrar接口的實(shí)現(xiàn)類
                // 設(shè)置到配置類的importBeanDefinitionRegistrars屬性中
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // Candidate class is an ImportBeanDefinitionRegistrar ->
                    // delegate to it to register additional bean definitions
                    Class<?> candidateClass = candidate.loadClass();
                    ImportBeanDefinitionRegistrar registrar =
                            BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            registrar, this.environment, this.resourceLoader, this.registry);
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                } else {
                    // 其它情況下把這個類入隊(duì)到ConfigurationClassParser的importStack(隊(duì)列)屬性中
                    // 然后把這個類當(dāng)成是@Configuration注解修飾的類遞歸重頭開始解析這個類
                    this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    processConfigurationClass(candidate.asConfigClass(configClass));
                }
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to process import candidates for configuration class [" +
                    configClass.getMetadata().getClassName() + "]", ex);
        }
        finally {
            this.importStack.pop();
        }
    }
}

@Import可以引入普通類弦叶、Configuration類、ImportBeanDefinitionRegistrar和ImportSelector的實(shí)例妇多,不排斥這些類中也包含Import伤哺,所以方法中也包含一個processConfigurationClass遞歸。
ImportSelector和ImportBeanDefinitionRegistrar是Spring中兩個擴(kuò)展接口者祖,分別通過selectImports方法和registerBeanDefinitions方法向容器注入Bean立莉。
另外ImportSelector還有一個子接口DeferredImportSelectors,這
個接口的實(shí)現(xiàn)類會等到Configuration類解析完之后在進(jìn)行再進(jìn)行processImports處理七问。
它們也是springboot加載自動配置的使用的注入方式蜓耻。

step 4 處理@Bean注解
這個處理過程很容易理解:

// 獲取被@Bean注解修飾的方法,然后添加到配置類的beanMethods屬性中
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

至此一個基于注解的Configuration類就解析完成了械巡,各個Bean類的BeanDefinition也注冊進(jìn)了容器刹淌,等待實(shí)例化。

碼字不易讥耗,轉(zhuǎn)載請保留原文連接http://www.reibang.com/p/b61809506d0b

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末有勾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子古程,更是在濱河造成了極大的恐慌蔼卡,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挣磨,死亡現(xiàn)場離奇詭異雇逞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茁裙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門塘砸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呜达,你說我怎么就攤上這事谣蠢。” “怎么了查近?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵眉踱,是天一觀的道長。 經(jīng)常有香客問我霜威,道長谈喳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任戈泼,我火速辦了婚禮婿禽,結(jié)果婚禮上赏僧,老公的妹妹穿的比我還像新娘。我一直安慰自己扭倾,他們只是感情好淀零,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膛壹,像睡著了一般驾中。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上模聋,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天肩民,我揣著相機(jī)與錄音,去河邊找鬼链方。 笑死持痰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祟蚀。 我是一名探鬼主播工窍,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼暂题!你這毒婦竟也來了移剪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤薪者,失蹤者是張志新(化名)和其女友劉穎纵苛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體言津,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡攻人,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悬槽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怀吻。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖初婆,靈堂內(nèi)的尸體忽然破棺而出蓬坡,到底是詐尸還是另有隱情,我是刑警寧澤磅叛,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布屑咳,位于F島的核電站,受9級特大地震影響弊琴,放射性物質(zhì)發(fā)生泄漏兆龙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一敲董、第九天 我趴在偏房一處隱蔽的房頂上張望紫皇。 院中可真熱鬧慰安,春花似錦、人聲如沸聪铺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铃剔。三九已至锣杂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間番宁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工赖阻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝶押,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓火欧,卻偏偏與公主長得像棋电,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子苇侵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354