SpringBoot源碼解析 -- AutoConfigure的實現(xiàn)原理

SpringBoot深入理解 -- @AliasFor注解的作用
SpringBoot源碼解析 -- SpringBoot啟動過程
SpringBoot源碼解析 -- AutoConfigure的實現(xiàn)原理
SpringBoot源碼解析 -- @ComponentScan的實現(xiàn)原理
SpringBoot源碼解析 -- @Value,@Autowired實現(xiàn)原理
SpringBoot源碼解析 -- Tomcat百宇,SpringMVC啟動
SpringBoot源碼解析 -- Logging仇矾,Environment啟動

源碼分析基于spring boot 2.1

SpringBoot中使用@EnableAutoConfiguration注解啟動AutoConfigure功能

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

這里起作用的實際上是@Import和AutoConfigurationImportSelector。
@Import注解非常重要坝初,它是SpringBoot中AutoConfiguration功能的基礎。

前面解析SpringBoot啟動過程的文章說過,SpringBoot啟動時會注入ConfigurationClassPostProcessor,該PostProcessor正是處理@Import的類。

ConfigurationClassPostProcessor#postProcessBeanFactory -> ConfigurationClassPostProcessor#processConfigBeanDefinitions

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();

    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
            }
        }
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {   // #1
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }
    if (configCandidates.isEmpty()) {
        return;
    }

    ...
    
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);    //#2

    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        parser.parse(candidates);   //#3
        parser.validate();

        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());  // #4
        configClasses.removeAll(alreadyParsed);

        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        this.reader.loadBeanDefinitions(configClasses); // #5
        alreadyParsed.addAll(configClasses);

        candidates.clear();
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames(); // #6
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            for (ConfigurationClass configurationClass : alreadyParsed) {
                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
            }
            for (String candidateName : newCandidateNames) {
                if (!oldCandidateNames.contains(candidateName)) {
                    BeanDefinition bd = registry.getBeanDefinition(candidateName);  
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                            !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                        candidates.add(new BeanDefinitionHolder(bd, candidateName));    // #7
                    }
                }
            }
            candidateNames = newCandidateNames;
        }
    }
    while (!candidates.isEmpty());  // #8

    ...
}

#1 檢查bean是否為ConfigurationClass站刑,這里主要是檢查class注解信息(spring中將@Configuration標注的類歸類為ConfigurationClass)
#2 構建ConfigurationClassParser
#3 解析ConfigurationClass
#4 獲取結果,注意ConfigurationClassParser#getConfigurationClasses方法獲取ConfigurationClassParser的處理結果
#5 獲取ConfigurationClass引入的Class鼻百,將其轉(zhuǎn)化為BeanDefinition绞旅,并注冊到Spring上下文
最后構造bean,是在AbstractApplicationContext#refresh方法中温艇,調(diào)用finishBeanFactoryInitialization因悲,構建熱加載的單例bean時完成。
#6 獲取新的BeanDefinition列表
#7 如果前面的ConfigurationClass有引入了新的ConfigurationClass中贝,添加到待處理集合
#8 循環(huán)處理囤捻,直到待處理集合為空

ConfigurationClassParser#parse -> ConfigurationClassParser#processConfigurationClass

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {    // #1
            return;
        }

        ...

        SourceClass sourceClass = asSourceClass(configClass);   // #2
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);    // #3
        }
        while (sourceClass != null);    // #4

        this.configurationClasses.put(configClass, configClass);    // #5
    }

#1 檢查ConfigurationClass是否存在@Conditional注解,如果存在邻寿,取注解中Condition條件判斷類進行判斷
#2 將ConfigurationClass轉(zhuǎn)化為SourceClass
SourceClass對Class元數(shù)據(jù)進行封裝蝎土,可以兼容處理JVM加載的Class和ASM讀取的元數(shù)據(jù),獲取元數(shù)據(jù)中注解绣否,方法等信息
#3 doProcessConfigurationClass方法很關鍵誊涯,處理@Component,@PropertySources蒜撮,@ComponentScans暴构,@Import,@ImportResource段磨,帶@Bean的方法取逾,接口及父類。
#4 如果ConfigurationClass存在父類苹支,doProcessConfigurationClass返回父類砾隅,這里遞歸處理父類數(shù)據(jù)
#5 將該ConfigurationClass加入configurationClasses,以便ConfigurationClassPostProcessor#processConfigBeanDefinitions方法#5步驟使用
該方法是處理ConfigurationClass的入口债蜜,doProcessConfigurationClass中引入了新的ConfigurationClass晴埂,也會調(diào)用該方法處理。

ConfigurationClassParser#doProcessConfigurationClass

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) { 
        processMemberClasses(configClass, sourceClass); // #1
    }

    ...

    processImports(configClass, sourceClass, getImports(sourceClass), true);    // #2

    AnnotationAttributes importResource =
            AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);   // #3
    if (importResource != null) {
        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);
        }
    }

    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);  // #4
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    processInterfaces(configClass, sourceClass);    // #5

    if (sourceClass.getMetadata().hasSuperClass()) {    // #6
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (superclass != null && !superclass.startsWith("java") &&
                !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            return sourceClass.getSuperClass();
        }
    }

    return null;
}

這里只關注該方法AutoConfigure功能相關的代碼
#1 如果Class存在@Component注解寻定,會查詢Class的內(nèi)部類儒洛,如果內(nèi)部類也是ConfigurationClass,會調(diào)用processConfigurationClass方法處理內(nèi)部類(注意狼速,@Configuration注解上標識了@Component注解)
#2 處理@Import注解
#3 處理@ImportResource琅锻,添加引入資源信息到ConfigurationClass#importedResources,ConfigurationClassPostProcessor#processConfigBeanDefinitions方法#5步驟會處理
#4 選擇Class中存在@Bean標注的方法,加入到ConfigurationClass#beanMethods中
#5 選擇接口中存在@Bean標注的方法恼蓬,同樣加入到ConfigurationClass#beanMethods中
#6 如果存在父類沫浆,返回父類到ConfigurationClassParser#processConfigurationClass中,遞歸處理父類滚秩。

ConfigurationClassParser#processImports

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
        Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
        return;
    }

    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) { // #1
                    Class<?> candidateClass = candidate.loadClass();
                    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            selector, this.environment, this.resourceLoader, this.registry);    
                    if (selector instanceof DeferredImportSelector) {   // #2
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    }
                    else {
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());   // #3
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);    // #4
                        processImports(configClass, currentSourceClass, importSourceClasses, false);    //#5
                    }
                }
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // #6
                    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());  //#7
                }
                else {  
                    this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    processConfigurationClass(candidate.asConfigClass(configClass));    //#8
                }
            }
        }
        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();
        }
    }
}

#1 @Import引入的類是ImportSelector實現(xiàn)類
#2 DeferredImportSelector接口需要延遲處理,加入到deferredImportSelectorHandler中
ConfigurationClassParser#parse方法最后會調(diào)用deferredImportSelectorHandler#process方法處理DeferredImportSelector接口
#3 調(diào)用ImportSelector#selectImports方法
#4 使用ImportSelector#selectImports返回Class Name數(shù)組淮捆,加載對應的SourceClass
#5 使用processImports方法繼續(xù)處理這些SourceClass
#6 @Import引入的類是ImportBeanDefinitionRegistrar實現(xiàn)類
#7 將該類加入到ConfigurationClass#importBeanDefinitionRegistrars中郁油,ConfigurationClassPostProcessor#processConfigBeanDefinitions方法#5步驟會處理
#8 @Import引入的類是其他類,轉(zhuǎn)發(fā)為ConfigurationClass攀痊,使用processConfigurationClass方法處理

這里對應了@Import注解的三種用法桐腌,引入ImportSelector,ImportBeanDefinitionRegistrar或者具體的ConfigurationClass苟径。

@Import最后都要processConfigurationClass處理它引入的ConfigurationClass

回到ConfigurationClassPostProcessor#processConfigBeanDefinitions方法#5步驟案站,
ConfigurationClassBeanDefinitionReader#loadBeanDefinitions -> loadBeanDefinitionsForConfigurationClass

private void loadBeanDefinitionsForConfigurationClass(
        ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    ...

    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);   // #1
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);   // #2
    }

    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());   // #3
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); // #4
}

#1 注冊ConfigurationClass自身的BeanDefinition
#2 注冊@Bean注解標識方法引入的bean
#3 從@ImportResource引入的資源中讀取BeanDefinition
#4 處理@Import引入的ImportBeanDefinitionRegistrar,調(diào)用ImportBeanDefinitionRegistrar#registerBeanDefinitions方法

@EnableAutoConfiguration注解引入的AutoConfigurationImportSelector棘街,實現(xiàn)的是DeferredImportSelector接口
AutoConfigurationImportSelector#selectImports -> getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);   // #1
    configurations = removeDuplicates(configurations);  // #2
    Set<String> exclusions = getExclusions(annotationMetadata, attributes); 
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);   // #3
    configurations = filter(configurations, autoConfigurationMetadata); // #4
    fireAutoConfigurationImportEvents(configurations, exclusions);  // #5
    return new AutoConfigurationEntry(configurations, exclusions);
}

#1 從spring.factories文件中獲取@EnableAutoConfiguration對應的ConfigurationClass
#3 排除spring.autoconfigure.exclude配置的ConfigurationClass
#4 使用spring.factories中配置的AutoConfigurationImportFilter的實現(xiàn)類(OnBeanCondition蟆盐,OnClassCondition,OnWebApplicationCondition)過濾部分ConfigurationClass疏遏,這里處理@ConditionalOnBean锄开,@ConditionalOnClass颠通,@ConditionalOnMissingClass等注解。
OnClassCondition可以判斷當前Java應用中存在或者不存在某一個class痹愚,SpringBoot AutoConfigure功能可以實現(xiàn)當我們引入某個框架jar后,自動配置完成該框架的配置蛔糯,正是通過該條件判斷類實現(xiàn)拯腮。

來看一個例子,RedisAutoConfiguration

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    ...
}

RedisAutoConfiguration是一個ConfigurationClass蚁飒,他使用@Bean標識方法引入其他bean
(RedisAutoConfiguration在spring-boot-autoconfigure這個jar下的spring.factories文件中已經(jīng)被配置為@EnableAutoConfiguration的ConfigurationClass)

@ConditionalOnClass表明classpath只有存在RedisOperations這個類动壤,RedisAutoConfiguration的配置才生效
(引入spring-data-redis的jar后有這個類了,RedisAutoConfiguration也就生效了)
@Import引入的 LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class用于與redis建立連接飒箭,并生成RedisConnectionFactory狼电。
同樣,引入 Lettuce相關jar后弦蹂,LettuceConnectionConfiguration生效肩碟,引入Jedis相關jar后,JedisConnectionConfiguration生效凸椿。

如果您覺得本文不錯削祈,歡迎關注我的微信公眾號,您的關注是我堅持的動力!


最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末髓抑,一起剝皮案震驚了整個濱河市咙崎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吨拍,老刑警劉巖褪猛,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異羹饰,居然都是意外死亡伊滋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門队秩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笑旺,“玉大人,你說我怎么就攤上這事馍资⊥仓鳎” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵鸟蟹,是天一觀的道長乌妙。 經(jīng)常有香客問我,道長建钥,這世上最難降的妖魔是什么冠胯? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮锦针,結果婚禮上荠察,老公的妹妹穿的比我還像新娘。我一直安慰自己奈搜,他們只是感情好悉盆,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著馋吗,像睡著了一般焕盟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宏粤,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天脚翘,我揣著相機與錄音,去河邊找鬼绍哎。 笑死来农,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的崇堰。 我是一名探鬼主播沃于,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼涩咖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了繁莹?” 一聲冷哼從身側(cè)響起檩互,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咨演,沒想到半個月后闸昨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡薄风,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年零院,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片村刨。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖撰茎,靈堂內(nèi)的尸體忽然破棺而出嵌牺,到底是詐尸還是另有隱情,我是刑警寧澤龄糊,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布逆粹,位于F島的核電站,受9級特大地震影響炫惩,放射性物質(zhì)發(fā)生泄漏僻弹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一他嚷、第九天 我趴在偏房一處隱蔽的房頂上張望蹋绽。 院中可真熱鬧,春花似錦筋蓖、人聲如沸卸耘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚣抗。三九已至,卻和暖如春瓮下,著一層夾襖步出監(jiān)牢的瞬間翰铡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工讽坏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锭魔,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓路呜,卻偏偏與公主長得像赂毯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359