溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot實現(xiàn)自動配置的方法

發(fā)布時間:2020-05-23 16:57:03 來源:億速云 閱讀:312 作者:鴿子 欄目:編程語言

1 前言

本篇接
助力SpringBoot自動配置的條件注解ConditionalOnXXX分析--SpringBoot源碼(三)

溫故而知新,我們來簡單回顧一下上篇的內(nèi)容,上一篇我們分析了SpringBoot的條件注解@ConditionalOnXxx的相關源碼,現(xiàn)挑重點總結如下:

  1. SpringBoot的所有@ConditionalOnXxx的條件類OnXxxCondition都是繼承于SpringBootCondition基類,而SpringBootCondition又實現(xiàn)了Condition接口。
  2. SpringBootCondition基類主要用來打印一些條件注解評估報告的日志,這些條件評估信息全部來源于其子類注解條件類OnXxxCondition,因此其也抽象了一個模板方法getMatchOutcome留給子類去實現(xiàn)來評估其條件注解是否符合條件。
  3. 前一篇我們也還有一個重要的知識點還沒分析,那就是跟過濾自動配置類邏輯有關的AutoConfigurationImportFilter接口,這篇文章我們來填一下這個坑。

前面我們分析了跟SpringBoot的自動配置息息相關內(nèi)置條件注解@ConditionalOnXxx后,現(xiàn)在我們就開始來擼SpringBoot自動配置的相關源碼了。

2 @SpringBootApplication注解

在開始前,我們先想一下,SpringBoot為何一個標注有@SpringBootApplication注解的啟動類通過執(zhí)行一個簡單的run方法就能實現(xiàn)SpringBoot大量Starter的自動配置呢?
其實SpringBoot的自動配置就跟@SpringBootApplication這個注解有關,我們先來看下其這個注解的源碼:

@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 {
// ...省略非關鍵代碼
}

@SpringBootApplication標注了很多注解,我們可以看到其中跟SpringBoot自動配置有關的注解就有一個即@EnableAutoConfiguration,因此,可以肯定的是SpringBoot的自動配置肯定跟@EnableAutoConfiguration息息相關(其中@ComponentScan注解的excludeFilters屬性也有一個類AutoConfigurationExcludeFilter,這個類跟自動配置也有點關系,但不是我們關注的重點)。
現(xiàn)在我們來打開@EnableAutoConfiguration注解的源碼:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

看到@EnableAutoConfiguration注解又標有@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)兩個注解,顧名思義,@AutoConfigurationPackage注解肯定跟自動配置的包有關,而AutoConfigurationImportSelector則是跟SpringBoot的自動配置選擇導入有關(Spring中的ImportSelector是用來導入配置類的,通常是基于某些條件注解@ConditionalOnXxxx來決定是否導入某個配置類)。

因此,可以看出AutoConfigurationImportSelector類是我們本篇的重點,因為SpringBoot的自動配置肯定有一個配置類,而這個配置類的導入則需要靠AutoConfigurationImportSelector這個哥們來實現(xiàn)。

接下來我們重點來看AutoConfigurationImportSelector這個類,完了我們再簡單分析下@AutoConfigurationPackage這個注解的邏輯。

3 如何去找SpringBoot自動配置實現(xiàn)邏輯的入口方法?

可以肯定的是SpringBoot的自動配置的邏輯肯定與AutoConfigurationImportSelector這個類有關,那么我們該如何去找到SpringBoot自動配置實現(xiàn)邏輯的入口方法呢?

在找SpringBoot自動配置實現(xiàn)邏輯的入口方法前,我們先來看下AutoConfigurationImportSelector的相關類圖,好有個整體的理解??聪聢D:


<center>圖1</center>

可以看到AutoConfigurationImportSelector重點是實現(xiàn)了DeferredImportSelector接口和各種Aware接口,然后DeferredImportSelector接口又繼承了ImportSelector接口。

自然而然的,我們會去關注AutoConfigurationImportSelector復寫DeferredImportSelector接口的實現(xiàn)方法selectImports方法,因為selectImports方法跟導入自動配置類有關,而這個方法往往是程序執(zhí)行的入口方法。經(jīng)過調(diào)試發(fā)現(xiàn)selectImports方法很具有迷惑性,selectImports方法跟自動配置相關的邏輯有點關系,但實質關系不大。

此時劇情的發(fā)展好像不太符合常理,此時我們又該如何來找到自動配置邏輯有關的入口方法呢?

最簡單的方法就是在AutoConfigurationImportSelector類的每個方法都打上斷點,然后調(diào)試看先執(zhí)行到哪個方法。但是我們可以不這么做,我們回想下,自定義一個Starter的時候我們是不是要在spring.factories配置文件中配置

EnableAutoConfiguration=XxxAutoConfiguration

因此可以推斷,SpringBoot的自動配置原理肯定跟從spring.factories配置文件中加載自動配置類有關,于是結合AutoConfigurationImportSelector的方法注釋,我們找到了getAutoConfigurationEntry方法。于是我們在這個方法里面打上一個斷點,此時通過調(diào)用棧幀來看下更上層的入口方法在哪里,然后我們再從跟自動配置相關的更上層的入口方法開始分析。


<center>圖2</center>

通過圖1我們可以看到,跟自動配置邏輯相關的入口方法在DeferredImportSelectorGrouping類的getImports方法處,因此我們就從DeferredImportSelectorGrouping類的getImports方法來開始分析SpringBoot的自動配置源碼好了。

4 分析SpringBoot自動配置原理

既然找到ConfigurationClassParser.getImports()方法是自動配置相關的入口方法,那么下面我們就來真正分析SpringBoot自動配置的源碼了。

先看一下getImports方法代碼:

// ConfigurationClassParser.java

public Iterable<Group.Entry> getImports() {
    // 遍歷DeferredImportSelectorHolder對象集合deferredImports,deferredImports集合裝了各種ImportSelector,當然這里裝的是AutoConfigurationImportSelector
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        // 【1】,利用AutoConfigurationGroup的process方法來處理自動配置的相關邏輯,決定導入哪些配置類(這個是我們分析的重點,自動配置的邏輯全在這了)
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                deferredImport.getImportSelector());
    }
    // 【2】,經(jīng)過上面的處理后,然后再進行選擇導入哪些配置類
    return this.group.selectImports();
}

【1】處的的代碼是我們分析的重中之重,自動配置的相關的絕大部分邏輯全在這里了,將在<font color=blue>4.1 分析自動配置的主要邏輯</font>深入分析。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(),<br/>deferredImport.getImportSelector());主要做的事情就是在this.groupAutoConfigurationGroup對象的process方法中,傳入的AutoConfigurationImportSelector對象來選擇一些符合條件的自動配置類,過濾掉一些不符合條件的自動配置類,就是這么個事情,無他。

注:

  1. AutoConfigurationGroup:是AutoConfigurationImportSelector的內(nèi)部類,主要用來處理自動配置相關的邏輯,擁有processselectImports方法,然后擁有entriesautoConfigurationEntries集合屬性,這兩個集合分別存儲被處理后的符合條件的自動配置類,我們知道這些就足夠了;
  2. AutoConfigurationImportSelector:承擔自動配置的絕大部分邏輯,負責選擇一些符合條件的自動配置類;
  3. metadata:標注在SpringBoot啟動類上的@SpringBootApplication注解元數(shù)據(jù)

【2】this.group.selectImports的方法主要是針對前面的process方法處理后的自動配置類再進一步有選擇的選擇導入,將在<font color=blue>4.2 有選擇的導入自動配置類</font>這小節(jié)深入分析。

4.1 分析自動配置的主要邏輯

這里繼續(xù)深究前面<font color=Blue> 4 分析SpringBoot自動配置原理</font>這節(jié)標【1】處的
this.group.process方法是如何處理自動配置相關邏輯的。

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

// 這里用來處理自動配置類,比如過濾掉不符合匹配條件的自動配置類
public void process(AnnotationMetadata annotationMetadata,
        DeferredImportSelector deferredImportSelector) {
    Assert.state(
            deferredImportSelector instanceof AutoConfigurationImportSelector,
            () -> String.format("Only %s implementations are supported, got %s",
                    AutoConfigurationImportSelector.class.getSimpleName(),
                    deferredImportSelector.getClass().getName()));
    // 【1】,調(diào)用getAutoConfigurationEntry方法得到自動配置類放入autoConfigurationEntry對象中
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
            .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                    annotationMetadata);
    // 【2】,又將封裝了自動配置類的autoConfigurationEntry對象裝進autoConfigurationEntries集合
    this.autoConfigurationEntries.add(autoConfigurationEntry); 
    // 【3】,遍歷剛獲取的自動配置類
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        // 這里符合條件的自動配置類作為key,annotationMetadata作為值放進entries集合
        this.entries.putIfAbsent(importClassName, annotationMetadata); 
    }
}

上面代碼中我們再來看標【1】的方法getAutoConfigurationEntry,這個方法主要是用來獲取自動配置類有關,承擔了自動配置的主要邏輯。直接上代碼:

// AutoConfigurationImportSelector.java

// 獲取符合條件的自動配置類,避免加載不必要的自動配置類從而造成內(nèi)存浪費
protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    // 獲取是否有配置spring.boot.enableautoconfiguration屬性,默認返回true
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 獲得@Congiguration標注的Configuration類即被審視introspectedClass的注解數(shù)據(jù),
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 將會獲取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解數(shù)據(jù)
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 【1】得到spring.factories文件配置的所有自動配置類
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    // 利用LinkedHashSet移除重復的配置類
    configurations = removeDuplicates(configurations);
    // 得到要排除的自動配置類,比如注解屬性exclude的配置類
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 將會獲取到exclude = FreeMarkerAutoConfiguration.class的注解數(shù)據(jù)
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 檢查要被排除的配置類,因為有些不是自動配置類,故要拋出異常
    checkExcludedClasses(configurations, exclusions);
    // 【2】將要排除的配置類移除
    configurations.removeAll(exclusions);
    // 【3】因為從spring.factories文件獲取的自動配置類太多,如果有些不必要的自動配置類都加載進內(nèi)存,會造成內(nèi)存浪費,因此這里需要進行過濾
    // 注意這里會調(diào)用AutoConfigurationImportFilter的match方法來判斷是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面會重點分析一下
    configurations = filter(configurations, autoConfigurationMetadata);
    // 【4】獲取了符合條件的自動配置類后,此時觸發(fā)AutoConfigurationImportEvent事件,
    // 目的是告訴ConditionEvaluationReport條件評估報告器對象來記錄符合條件的自動配置類
    // 該事件什么時候會被觸發(fā)?--> 在刷新容器時調(diào)用invokeBeanFactoryPostProcessors后置處理器時觸發(fā)
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 【5】將符合條件和要排除的自動配置類封裝進AutoConfigurationEntry對象,并返回
    return new AutoConfigurationEntry(configurations, exclusions); 
}

AutoConfigurationEntry方法主要做的事情就是獲取符合條件的自動配置類,避免加載不必要的自動配置類從而造成內(nèi)存浪費。我們下面總結下AutoConfigurationEntry方法主要做的事情:

【1】從spring.factories配置文件中加載EnableAutoConfiguration自動配置類,獲取的自動配置類如圖3所示。這里我們知道該方法做了什么事情就行了,后面還會有一篇文章詳述spring.factories的原理;

【2】若@EnableAutoConfiguration等注解標有要exclude的自動配置類,那么再將這個自動配置類排除掉;

【3】排除掉要exclude的自動配置類后,然后再調(diào)用filter方法進行進一步的過濾,再次排除一些不符合條件的自動配置類;這個在稍后會詳細分析。

【4】經(jīng)過重重過濾后,此時再觸發(fā)AutoConfigurationImportEvent事件,告訴ConditionEvaluationReport條件評估報告器對象來記錄符合條件的自動配置類;(這個在<font color=Blue>6 AutoConfigurationImportListener</font>這小節(jié)詳細分析。)

【5】 最后再將符合條件的自動配置類返回。


<center>圖3</center>

總結了AutoConfigurationEntry方法主要的邏輯后,我們再來細看一下AutoConfigurationImportSelectorfilter方法:

// AutoConfigurationImportSelector.java

private List<String> filter(List<String> configurations,
            AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    // 將從spring.factories中獲取的自動配置類轉出字符串數(shù)組
    String[] candidates = StringUtils.toStringArray(configurations);
    // 定義skip數(shù)組,是否需要跳過。注意skip數(shù)組與candidates數(shù)組順序一一對應
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    // getAutoConfigurationImportFilters方法:拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
    // 然后遍歷這三個條件類去過濾從spring.factories加載的大量配置類
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // 調(diào)用各種aware方法,將beanClassLoader,beanFactory等注入到filter對象中,
        // 這里的filter對象即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
        invokeAwareMethods(filter);
        // 判斷各種filter來判斷每個candidate(這里實質要通過candidate(自動配置類)拿到其標注的
        // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配,
        // 注意candidates數(shù)組與match數(shù)組一一對應
        /**********************【主線,重點關注】********************************/
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        // 遍歷match數(shù)組,注意match順序跟candidates的自動配置類一一對應
        for (int i = 0; i < match.length; i++) {
            // 若有不匹配的話
            if (!match[i]) {
                // 不匹配的將記錄在skip數(shù)組,標志skip[i]為true,也與candidates數(shù)組一一對應
                skip[i] = true;
                // 因為不匹配,將相應的自動配置類置空
                candidates[i] = null;
                // 標注skipped為true
                skipped = true; 
            }
        }
    } 
    // 這里表示若所有自動配置類經(jīng)過OnBeanCondition,OnClassCondition和OnWebApplicationCondition過濾后,全部都匹配的話,則全部原樣返回
    if (!skipped) {
        return configurations;
    }
    // 建立result集合來裝匹配的自動配置類
    List<String> result = new ArrayList<>(candidates.length); 
    for (int i = 0; i < candidates.length; i++) {
        // 若skip[i]為false,則說明是符合條件的自動配置類,此時添加到result集合中
        if (!skip[i]) { 
            result.add(candidates[i]);
        }
    }
    // 打印日志
    if (logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
                + " ms");
    }
    // 最后返回符合條件的自動配置類
    return new ArrayList<>(result);
}

AutoConfigurationImportSelectorfilter方法主要做的事情就是調(diào)用AutoConfigurationImportFilter接口的match方法來判斷每一個自動配置類上的條件注解(若有的話)@ConditionalOnClass,@ConditionalOnBean@ConditionalOnWebApplication是否滿足條件,若滿足,則返回true,說明匹配,若不滿足,則返回false說明不匹配。

我們現(xiàn)在知道AutoConfigurationImportSelectorfilter方法主要做了什么事情就行了,現(xiàn)在先不用研究的過深,至于AutoConfigurationImportFilter接口的match方法將在<font color=Blue>5 AutoConfigurationImportFilter</font>這小節(jié)再詳細分析,填補一下我們前一篇條件注解源碼分析中留下的坑。

注意:我們堅持主線優(yōu)先的原則,其他枝節(jié)代碼這里不深究,以免丟了主線哈。

4.2 有選擇的導入自動配置類

這里繼續(xù)深究前面<font color=Blue> 4 分析SpringBoot自動配置原理</font>這節(jié)標【2】處的
this.group.selectImports方法是如何進一步有選擇的導入自動配置類的。直接看代碼:

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    } 
    // 這里得到所有要排除的自動配置類的set集合
    Set<String> allExclusions = this.autoConfigurationEntries.stream()
            .map(AutoConfigurationEntry::getExclusions)
            .flatMap(Collection::stream).collect(Collectors.toSet());
    // 這里得到經(jīng)過濾后所有符合條件的自動配置類的set集合
    Set<String> processedConfigurations = this.autoConfigurationEntries.stream() 
            .map(AutoConfigurationEntry::getConfigurations)
            .flatMap(Collection::stream)
            .collect(Collectors.toCollection(LinkedHashSet::new));
    // 移除掉要排除的自動配置類
    processedConfigurations.removeAll(allExclusions); 
    // 對標注有@Order注解的自動配置類進行排序,
    return sortAutoConfigurations(processedConfigurations,
            getAutoConfigurationMetadata())
                    .stream()
                    .map((importClassName) -> new Entry(
                            this.entries.get(importClassName), importClassName))
                    .collect(Collectors.toList());
}

可以看到,selectImports方法主要是針對經(jīng)過排除掉exclude的和被AutoConfigurationImportFilter接口過濾后的滿足條件的自動配置類再進一步排除exclude的自動配置類,然后再排序。邏輯很簡單,不再詳述。

不過有個疑問,前面已經(jīng)exclude過一次了,為何這里還要再exclude一次?

5 AutoConfigurationImportFilter

這里繼續(xù)深究前面<font color=Blue> 4.1節(jié)</font>的
AutoConfigurationImportSelector.filter方法的過濾自動配置類的boolean[] match = filter.match(candidates, autoConfigurationMetadata);這句代碼。

因此我們繼續(xù)分析AutoConfigurationImportFilter接口,分析其match方法,同時也是對前一篇@ConditionalOnXxx的源碼分析文章中留下的坑進行填補。

AutoConfigurationImportFilter接口只有一個match方法用來過濾不符合條件的自動配置類。

@FunctionalInterface
public interface AutoConfigurationImportFilter {
    boolean[] match(String[] autoConfigurationClasses,
            AutoConfigurationMetadata autoConfigurationMetadata);
}

同樣,在分析AutoConfigurationImportFilter接口的match方法前,我們先來看下其類關系圖:


<center>圖4</center>

可以看到,AutoConfigurationImportFilter接口有一個具體的實現(xiàn)類FilteringSpringBootCondition,FilteringSpringBootCondition又有三個具體的子類:OnClassCondition,OnBeanCondtitionOnWebApplicationCondition

那么這幾個類之間的關系是怎樣的呢?

FilteringSpringBootCondition實現(xiàn)了AutoConfigurationImportFilter接口的match方法,然后在FilteringSpringBootConditionmatch方法調(diào)用getOutcomes這個抽象模板方法返回自動配置類的匹配與否的信息。同時,最重要的是FilteringSpringBootCondition的三個子類OnClassCondition,OnBeanCondtitionOnWebApplicationCondition將會復寫這個模板方法實現(xiàn)自己的匹配判斷邏輯。

好了,AutoConfigurationImportFilter接口的整體關系已經(jīng)清楚了,現(xiàn)在我們再進入其具體實現(xiàn)類FilteringSpringBootConditionmatch方法看看是其如何根據(jù)條件過濾自動配置類的。

// FilteringSpringBootCondition.java

@Override
public boolean[] match(String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    // 創(chuàng)建評估報告
    ConditionEvaluationReport report = ConditionEvaluationReport
            .find(this.beanFactory);
    // 注意getOutcomes是模板方法,將spring.factories文件種加載的所有自動配置類傳入
    // 子類(這里指的是OnClassCondition,OnBeanCondition和OnWebApplicationCondition類)去過濾
    // 注意outcomes數(shù)組存儲的是不匹配的結果,跟autoConfigurationClasses數(shù)組一一對應
    /*****************************【主線,重點關注】*********************************************/
    ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
            autoConfigurationMetadata);
    boolean[] match = new boolean[outcomes.length];
    // 遍歷outcomes,這里outcomes為null則表示匹配,不為null則表示不匹配
    for (int i = 0; i < outcomes.length; i++) {
        ConditionOutcome outcome = outcomes[i];
        match[i] = (outcome == null || outcome.isMatch());
        if (!match[i] && outcomes[i] != null) {
            // 這里若有某個類不匹配的話,此時調(diào)用父類SpringBootCondition的logOutcome方法打印日志
            logOutcome(autoConfigurationClasses[i], outcomes[i]);
            // 并將不匹配情況記錄到report
            if (report != null) {
                report.recordConditionEvaluation(autoConfigurationClasses[i], this,
                        outcomes[i]);
            }
        }
    }
    return match;
}

FilteringSpringBootConditionmatch方法主要做的事情還是調(diào)用抽象模板方法getOutcomes來根據(jù)條件來過濾自動配置類,而復寫getOutcomes模板方法的有三個子類,這里不再一一分析,只挑選OnClassCondition復寫的getOutcomes方法進行分析。

5.1 OnClassCondition

先直接上OnClassCondition復寫的getOutcomes方法的代碼:

// OnClassCondition.java

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    // Split the work and perform half in a background thread. Using a single
    // additional thread seems to offer the best performance. More threads make
    // things worse
    // 這里經(jīng)過測試用兩個線程去跑的話性能是最好的,大于兩個線程性能反而變差
    int split = autoConfigurationClasses.length / 2;
    // 【1】開啟一個新線程去掃描判斷已經(jīng)加載的一半自動配置類
    OutcomesResolver firstHalfResolver = createOutcomesResolver(
            autoConfigurationClasses, 0, split, autoConfigurationMetadata);
    // 【2】這里用主線程去掃描判斷已經(jīng)加載的一半自動配置類
    OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
            autoConfigurationClasses, split, autoConfigurationClasses.length,
            autoConfigurationMetadata, getBeanClassLoader());
    // 【3】先讓主線程去執(zhí)行解析一半自動配置類是否匹配條件
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
    // 【4】這里用新開啟的線程取解析另一半自動配置類是否匹配
    // 注意為了防止主線程執(zhí)行過快結束,resolveOutcomes方法里面調(diào)用了thread.join()來
    // 讓主線程等待新線程執(zhí)行結束,因為后面要合并兩個線程的解析結果
    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
    // 新建一個ConditionOutcome數(shù)組來存儲自動配置類的篩選結果
    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    // 將前面兩個線程的篩選結果分別拷貝進outcomes數(shù)組
    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
    // 返回自動配置類的篩選結果
    return outcomes;
}

可以看到,OnClassConditiongetOutcomes方法主要解析自動配置類是否符合匹配條件,當然這個匹配條件指自動配置類上的注解@ConditionalOnClass指定的類存不存在于classpath中,存在則返回匹配,不存在則返回不匹配。

由于解析自動配置類是否匹配比較耗時,因此從上面代碼中我們可以看到分別創(chuàng)建了firstHalfResolversecondHalfResolver兩個解析對象,這兩個解析對象個分別對應一個線程去解析加載的自動配置類是否符合條件,最終將兩個線程的解析自動配置類的匹配結果合并后返回。

那么自動配置類是否符合條件的解析判斷過程又是怎樣的呢?現(xiàn)在我們分別來看一下上面代碼注釋標注的【1】,【2】【3】【4】處。

5.1.1 createOutcomesResolver

這里對應前面<font color=blue>5.1節(jié)</font>的代碼注釋標注【1】處的OutcomesResolver firstHalfResolver = createOutcomesResolver(...);的方法:

// OnClassCondition.java

private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses,
        int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
    // 新建一個StandardOutcomesResolver對象
    OutcomesResolver outcomesResolver = new StandardOutcomesResolver(
            autoConfigurationClasses, start, end, autoConfigurationMetadata,
            getBeanClassLoader());
    try {
        // new一個ThreadedOutcomesResolver對象,并將StandardOutcomesResolver類型的outcomesResolver對象作為構造器參數(shù)傳入
        return new ThreadedOutcomesResolver(outcomesResolver);
    }
    // 若上面開啟的線程拋出AccessControlException異常,則返回StandardOutcomesResolver對象
    catch (AccessControlException ex) {
        return outcomesResolver;
    }
}

可以看到createOutcomesResolver方法創(chuàng)建了一個封裝了StandardOutcomesResolver類的ThreadedOutcomesResolver解析對象。
我們再來看下ThreadedOutcomesResolver這個線程解析類封裝StandardOutcomesResolver這個對象的目的是什么?我們繼續(xù)跟進代碼:

// OnClassCondtion.java

private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {
    // 這里開啟一個新的線程,這個線程其實還是利用StandardOutcomesResolver的resolveOutcomes方法
    // 對自動配置類進行解析判斷是否匹配
    this.thread = new Thread(
            () -> this.outcomes = outcomesResolver.resolveOutcomes());
    // 開啟線程
    this.thread.start();
}

可以看到在構造ThreadedOutcomesResolver對象時候,原來是開啟了一個線程,然后這個線程其實還是調(diào)用了剛傳進來的StandardOutcomesResolver對象的resolveOutcomes方法去解析自動配置類。具體如何解析呢?稍后我們在分析【3】處代碼secondHalfResolver.resolveOutcomes();的時候再深究。

5.1.2 new StandardOutcomesResolver

這里對應前面<font color=blue>5.1節(jié)</font>的【2】處的代碼OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(...);,邏輯很簡單,就是創(chuàng)建了一個StandardOutcomesResolver對象,用于后面解析自動配置類是否匹配,同時,新建的一個線程也是利用它來完成自動配置類的解析的。

5.1.3 StandardOutcomesResolver.resolveOutcomes方法

這里對應前面<font color=blue>5.1節(jié)</font>標注的【3】的代碼ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();。

這里StandardOutcomesResolver.resolveOutcomes方法承擔了解析自動配置類匹配與否的全部邏輯,是我們要重點分析的方法,resolveOutcomes方法最終把解析的自動配置類的結果賦給secondHalf數(shù)組。那么它是如何解析自動配置類是否匹配條件的呢?

// OnClassCondition$StandardOutcomesResolver.java

public ConditionOutcome[] resolveOutcomes() {
    // 再調(diào)用getOutcomes方法來解析
    return getOutcomes(this.autoConfigurationClasses, this.start, this.end,
            this.autoConfigurationMetadata);
}

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
        int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { // 只要autoConfigurationMetadata沒有存儲相關自動配置類,那么outcome默認為null,則說明匹配
    ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
    // 遍歷每一個自動配置類
    for (int i = start; i < end; i++) {
        String autoConfigurationClass = autoConfigurationClasses[i];
        // TODO 對于autoConfigurationMetadata有個疑問:為何有些自動配置類的條件注解能被加載到autoConfigurationMetadata,而有些又不能,比如自己定義的一個自動配置類HelloWorldEnableAutoConfiguration就沒有被存到autoConfigurationMetadata中
        if (autoConfigurationClass != null) {
            // 這里取出注解在AutoConfiguration自動配置類類的@ConditionalOnClass注解的指定類的全限定名,
            // 舉個栗子,看下面的KafkaStreamsAnnotationDrivenConfiguration這個自動配置類
            /**
             * @ConditionalOnClass(StreamsBuilder.class)
             * class KafkaStreamsAnnotationDrivenConfiguration {
             * // 省略無關代碼
             * }
             */
            // 那么取出的就是StreamsBuilder類的全限定名即candidates = org.apache.kafka.streams.StreamsBuilder
            String candidates = autoConfigurationMetadata
                    .get(autoConfigurationClass, "ConditionalOnClass"); // 因為這里是處理某個類是否存在于classpath中,所以傳入的key是ConditionalOnClass
            // 若自動配置類標有ConditionalOnClass注解且有值,此時調(diào)用getOutcome判斷是否存在于類路徑中
            if (candidates != null) {
                // 拿到自動配置類注解@ConditionalOnClass的值后,再調(diào)用getOutcome方法去判斷匹配結果,若該類存在于類路徑,則getOutcome返回null,否則非null
                /*******************【主線,重點關注】******************/
                outcomes[i - start] = getOutcome(candidates);
            }
        }
    }
    return outcomes;
}

可以看到StandardOutcomesResolver.resolveOutcomes的方法中再次調(diào)用getOutcomes方法,主要是從autoConfigurationMetadata對象中獲取到自動配置類上的注解@ConditionalOnClass指定的類的全限定名,然后作為參數(shù)傳入getOutcome方法用于去類路徑加載該類,若能加載到則說明注解@ConditionalOnClass滿足條件,此時說明自動配置類匹配成功。

但是別忘了,這里只是過了@ConditionalOnClass注解這一關,若自動配置類還有其他注解比如@ConditionalOnBean,若該@ConditionalOnBean注解不滿足條件的話,同樣最終結果是不匹配的。這里扯的有點遠,我們回到OnClassCondtion的判斷邏輯,繼續(xù)進入getOutcome方法看它是如何去判斷@ConditionalOnClass注解滿不滿足條件的。

// OnClassCondition$StandardOutcomesResolver.java

// 返回的outcome記錄的是不匹配的情況,不為null,則說明不匹配;為null,則說明匹配
private ConditionOutcome getOutcome(String candidates) {
    // candidates的形式為“org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.ConditionalOnClass=org.aspectj.lang.annotation.Aspect,org.aspectj.lang.reflect.Advice,org.aspectj.weaver.AnnotatedElement”
    try {// 自動配置類上@ConditionalOnClass的值只有一個的話,直接調(diào)用getOutcome方法判斷是否匹配
        if (!candidates.contains(",")) {
            // 看到因為傳入的參數(shù)是 ClassNameFilter.MISSING,因此可以猜測這里應該是得到不匹配的結果
            /******************【主線,重點關注】********************/
            return getOutcome(candidates, ClassNameFilter.MISSING, 
                    this.beanClassLoader);
        }
        // 自動配置類上@ConditionalOnClass的值有多個的話,則遍歷每個值(其值以逗號,分隔)
        for (String candidate : StringUtils
                .commaDelimitedListToStringArray(candidates)) {
            ConditionOutcome outcome = getOutcome(candidate,
                    ClassNameFilter.MISSING, this.beanClassLoader);
            // 可以看到,這里只要有一個不匹配的話,則返回不匹配結果
            if (outcome != null) { 
                return outcome;
            }
        }
    }
    catch (Exception ex) {
        // We'll get another chance later
    }
    return null;
}

可以看到,getOutcome方法再次調(diào)用重載方法getOutcome進一步去判斷注解@ConditionalOnClass指定的類存不存在類路徑中,跟著主線繼續(xù)跟進去:

// OnClassCondition$StandardOutcomesResolver.java

private ConditionOutcome getOutcome(String className,
        ClassNameFilter classNameFilter, ClassLoader classLoader) {
    // 調(diào)用classNameFilter的matches方法來判斷`@ConditionalOnClass`指定的類存不存在類路徑中
    /******************【主線,重點關注】********************/
    if (classNameFilter.matches(className, classLoader)) { // 這里調(diào)用classNameFilter去判斷className是否存在于類路徑中,其中ClassNameFilter又分為PRESENT和MISSING兩種;目前只看到ClassNameFilter為MISSING的調(diào)用情況,所以默認為true的話記錄不匹配信息;若傳入ClassNameFilter為PRESENT的話,估計還要再寫一個else分支
        return ConditionOutcome.noMatch(ConditionMessage
                .forCondition(ConditionalOnClass.class)
                .didNotFind("required class").items(Style.QUOTE, className));
    }
    return null;
}

我們一層一層的剝,最終剝到了最底層了,這個真的需要足夠耐心,沒辦法,源碼只能一點一點的啃,嘿嘿??梢钥吹阶罱K是調(diào)用ClassNameFiltermatches方法來判斷@ConditionalOnClass指定的類存不存在類路徑中,若不存在的話,則返回不匹配。

我們繼續(xù)跟進ClassNameFilter的源碼:

// FilteringSpringBootCondition.java

protected enum ClassNameFilter {
    // 這里表示指定的類存在于類路徑中,則返回true
    PRESENT {

        @Override
        public boolean matches(String className, ClassLoader classLoader) {
            return isPresent(className, classLoader);
        }

    },
    // 這里表示指定的類不存在于類路徑中,則返回true
    MISSING {

        @Override
        public boolean matches(String className, ClassLoader classLoader) {
            return !isPresent(className, classLoader); // 若classpath不存在className這個類,則返回true
        }

    };
    // 這又是一個抽象方法,分別被PRESENT和MISSING枚舉類實現(xiàn)
    public abstract boolean matches(String className, ClassLoader classLoader);
    // 檢查指定的類是否存在于類路徑中  
    public static boolean isPresent(String className, ClassLoader classLoader) {
        if (classLoader == null) {
            classLoader = ClassUtils.getDefaultClassLoader();
        }
        // 利用類加載器去加載相應類,若沒有拋出異常則說明類路徑中存在該類,此時返回true
        try {
            forName(className, classLoader); 
            return true;
        }// 若不存在于類路徑中,此時拋出的異常將catch住,返回false。
        catch (Throwable ex) {
            return false;
        }
    }
    // 利用類加載器去加載指定的類
    private static Class<?> forName(String className, ClassLoader classLoader)
            throws ClassNotFoundException {
        if (classLoader != null) {
            return classLoader.loadClass(className);
        }
        return Class.forName(className);
    }

}

可以看到ClassNameFilter原來是FilteringSpringBootCondition的一個內(nèi)部枚舉類,其實現(xiàn)了判斷指定類是否存在于classpath中的邏輯,這個類很簡單,這里不再詳述。

5.1.4 ThreadedOutcomesResolver.resolveOutcomes方法

這里對應前面<font color=blue>5.1節(jié)</font>的標注的【4】的代碼ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes()

前面分析<font color=blue>5.1.3 StandardOutcomesResolver.resolveOutcomes</font>方法已經(jīng)刨根追底,陷入細節(jié)比較深,現(xiàn)在我們需要跳出來繼續(xù)看前面標注的【4】的代碼ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes()的方法哈。

這里是用新開啟的線程去調(diào)用StandardOutcomesResolver.resolveOutcomes方法解析另一半自動配置類是否匹配,因為是新線程,這里很可能會出現(xiàn)這么一種情況:主線程解析完屬于自己解析的一半自動配置類后,那么久繼續(xù)往下跑了,此時不會等待新開啟的子線程的。

因此,為了讓主線程解析完后,我們需要讓主線程繼續(xù)等待正在解析的子線程,直到子線程結束。那么我們繼續(xù)跟進代碼區(qū)看下ThreadedOutcomesResolver.resolveOutcomes方法是怎樣實現(xiàn)讓主線程等待子線程的:

// OnClassCondition$ThreadedOutcomesResolver.java

public ConditionOutcome[] resolveOutcomes() {
    try {
        // 調(diào)用子線程的Join方法,讓主線程等待
        this.thread.join();
    }
    catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
    }
    // 若子線程結束后,此時返回子線程的解析結果
    return this.outcomes;
}

可以看到用了Thread.join()方法來讓主線程等待正在解析自動配置類的子線程,這里應該也可以用CountDownLatch來讓主線程等待子線程結束。最終將子線程解析后的結果賦給firstHalf數(shù)組。

5.2 OnBeanCondition和OnWebApplicationCondition

前面<font color=blue>5.1 OnClassCondition</font>節(jié)深入分析了OnClassCondition是如何過濾自動配置類的,那么自動配置類除了要經(jīng)過OnClassCondition的過濾,還要經(jīng)過OnBeanConditionOnWebApplicationCondition這兩個條件類的過濾,這里不再詳述,有興趣的小伙伴可自行分析。

6 AutoConfigurationImportListener

這里繼續(xù)深究前面<font color=Blue> 4.1節(jié)</font>的
AutoConfigurationImportSelector.getAutoConfigurationEntry方法的觸發(fā)自動配置類過濾完畢的事件fireAutoConfigurationImportEvents(configurations, exclusions);這句代碼。

我們直接點進fireAutoConfigurationImportEvents方法看看其是如何觸發(fā)事件的:

// AutoConfigurationImportSelector.java

private void fireAutoConfigurationImportEvents(List<String> configurations,
        Set<String> exclusions) {
    // 從spring.factories總獲取到AutoConfigurationImportListener即ConditionEvaluationReportAutoConfigurationImportListener
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); 
    if (!listeners.isEmpty()) {
        // 新建一個AutoConfigurationImportEvent事件
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
                configurations, exclusions);
        // 遍歷剛獲取到的AutoConfigurationImportListener
        for (AutoConfigurationImportListener listener : listeners) {
            // 這里調(diào)用各種Aware方法用于觸發(fā)事件前賦值,比如設置factory,environment等
            invokeAwareMethods(listener);
            // 真正觸發(fā)AutoConfigurationImportEvent事件即回調(diào)listener的onXXXEveent方法。這里用于記錄自動配置類的評估信息
            listener.onAutoConfigurationImportEvent(event); 
        }
    }
}

如上,fireAutoConfigurationImportEvents方法做了以下兩件事情:

  1. 調(diào)用getAutoConfigurationImportListeners方法從spring.factoris配置文件獲取實現(xiàn)AutoConfigurationImportListener接口的事件監(jiān)聽器;如下圖,可以看到獲取的是ConditionEvaluationReportAutoConfigurationImportListener

  1. 遍歷獲取的各個事件監(jiān)聽器,然后調(diào)用監(jiān)聽器各種Aware方法給監(jiān)聽器賦值,最后再依次回調(diào)事件監(jiān)聽器的onAutoConfigurationImportEvent方法,執(zhí)行監(jiān)聽事件的邏輯。

此時我們再來看下ConditionEvaluationReportAutoConfigurationImportListener監(jiān)聽器監(jiān)聽到事件后,它的onAutoConfigurationImportEvent方法究竟做了哪些事情:

// ConditionEvaluationReportAutoConfigurationImportListener.java

public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
    if (this.beanFactory != null) {
        // 獲取到條件評估報告器對象
        ConditionEvaluationReport report = ConditionEvaluationReport
                .get(this.beanFactory);
        // 將符合條件的自動配置類記錄到unconditionalClasses集合中
        report.recordEvaluationCandidates(event.getCandidateConfigurations());
        // 將要exclude的自動配置類記錄到exclusions集合中
        report.recordExclusions(event.getExclusions()); 
    }
}

可以看到,ConditionEvaluationReportAutoConfigurationImportListener監(jiān)聽器監(jiān)聽到事件后,做的事情很簡單,只是分別記錄下符合條件和被exclude的自動配置類。

7 AutoConfigurationPackages

前面已經(jīng)詳述了SpringBoot的自動配置原理了,最后的最后,跟SpringBoot自動配置有關的注解@AutoConfigurationPackage還沒分析,我們來看下這個注解的源碼:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

可以看到@AutoConfigurationPackage注解是跟SpringBoot自動配置所在的包相關的,即將 添加該注解的類所在的package 作為 自動配置package 進行管理。

接下來我們再看看AutoConfigurationPackages.Registrar類是干嘛的,直接看源碼:

//AutoConfigurationPackages.Registrar.java

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }
}

可以看到Registrar類是AutoConfigurationPackages的靜態(tài)內(nèi)部類,實現(xiàn)了ImportBeanDefinitionRegistrarDeterminableImports兩個接口?,F(xiàn)在我們主要來關注下Registrar實現(xiàn)的registerBeanDefinitions方法,顧名思義,這個方法是注冊bean定義的方法??吹剿终{(diào)用了AutoConfigurationPackagesregister方法,繼續(xù)跟進源碼:

// AutoConfigurationPackages.java

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    if (registry.containsBeanDefinition(BEAN)) {
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition
                .getConstructorArgumentValues();
        constructorArguments.addIndexedArgumentValue(0,
                addBasePackages(constructorArguments, packageNames));
    }
    else {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
                packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

如上,可以看到register方法注冊了一個packageNames即自動配置類注解@EnableAutoConfiguration所在的所在的包名相關的bean。那么注冊這個bean的目的是為了什么呢?
結合官網(wǎng)注釋知道,注冊這個自動配置包名相關的bean是為了被其他地方引用,比如JPA entity scanner,具體拿來干什么久不知道了,這里不再深究了。

8 小結

好了,SpringBoot的自動配置的源碼分析就到這里了,比較長,有些地方也很深入細節(jié),讀完需要一定的耐心。

最后,我們再總結下SpringBoot自動配置的原理,主要做了以下事情:

  1. 從spring.factories配置文件中加載自動配置類;
  2. 加載的自動配置類中排除掉@EnableAutoConfiguration注解的exclude屬性指定的自動配置類;
  3. 然后再用AutoConfigurationImportFilter接口去過濾自動配置類是否符合其標注注解(若有標注的話)@ConditionalOnClass,@ConditionalOnBean@ConditionalOnWebApplication的條件,若都符合的話則返回匹配結果;
  4. 然后觸發(fā)AutoConfigurationImportEvent事件,告訴ConditionEvaluationReport條件評估報告器對象來分別記錄符合條件和exclude的自動配置類。
  5. 最后spring再將最后篩選后的自動配置類導入IOC容器中

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI