SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)

注:該源碼分析對應SpringBoot版本爲2.1.0.RELEASEjava

1 前言

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

溫故而知新,咱們來簡單回顧一下上篇的內容,上一篇咱們分析了SpringBoot的條件註解@ConditionalOnXxx的相關源碼,現挑重點總結以下:apache

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

前面咱們分析了跟SpringBoot的自動配置息息相關內置條件註解@ConditionalOnXxx後,如今咱們就開始來擼SpringBoot自動配置的相關源碼了。數組

2 @SpringBootApplication註解

在開始前,咱們先想一下,SpringBoot爲什麼一個標註有@SpringBootApplication註解的啓動類經過執行一個簡單的run方法就能實現SpringBoot大量Starter的自動配置呢?
其實SpringBoot的自動配置就跟@SpringBootApplication這個註解有關,咱們先來看下其這個註解的源碼:ide

@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,這個類跟自動配置也有點關係,但不是咱們關注的重點)。
如今咱們來打開@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來決定是否導入某個配置類)。post

所以,能夠看出AutoConfigurationImportSelector類是咱們本篇的重點,由於SpringBoot的自動配置確定有一個配置類,而這個配置類的導入則須要靠AutoConfigurationImportSelector這個哥們來實現。性能

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

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

能夠確定的是SpringBoot的自動配置的邏輯確定與AutoConfigurationImportSelector這個類有關,那麼咱們該如何去找到SpringBoot自動配置實現邏輯的入口方法呢?測試

在找SpringBoot自動配置實現邏輯的入口方法前,咱們先來看下AutoConfigurationImportSelector的相關類圖,好有個總體的理解。看下圖:

SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)
<center>圖1</center>

能夠看到AutoConfigurationImportSelector重點是實現了DeferredImportSelector接口和各類Aware接口,而後DeferredImportSelector接口又繼承了ImportSelector接口。

天然而然的,咱們會去關注AutoConfigurationImportSelector複寫DeferredImportSelector接口的實現方法selectImports方法,由於selectImports方法跟導入自動配置類有關,而這個方法每每是程序執行的入口方法。通過調試發現selectImports方法很具備迷惑性,selectImports方法跟自動配置相關的邏輯有點關係,但實質關係不大。

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

最簡單的方法就是在AutoConfigurationImportSelector類的每一個方法都打上斷點,而後調試看先執行到哪一個方法。可是咱們能夠不這麼作,咱們回想下,自定義一個Starter的時候咱們是否是要在spring.factories配置文件中配置

EnableAutoConfiguration=XxxAutoConfiguration

所以能夠推斷,SpringBoot的自動配置原理確定跟從spring.factories配置文件中加載自動配置類有關,因而結合AutoConfigurationImportSelector的方法註釋,咱們找到了getAutoConfigurationEntry方法。因而咱們在這個方法裏面打上一個斷點,此時經過調用棧幀來看下更上層的入口方法在哪裏,而後咱們再從跟自動配置相關的更上層的入口方法開始分析。

SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)
<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】,通過上面的處理後,而後再進行選擇導入哪些配置類
    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的內部類,主要用來處理自動配置相關的邏輯,擁有processselectImports方法,而後擁有entriesautoConfigurationEntries集合屬性,這兩個集合分別存儲被處理後的符合條件的自動配置類,咱們知道這些就足夠了;
  2. AutoConfigurationImportSelector:承擔自動配置的絕大部分邏輯,負責選擇一些符合條件的自動配置類;
  3. metadata:標註在SpringBoot啓動類上的@SpringBootApplication註解元數據

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

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

這裏繼續深究前面<font color=Blue> 4 分析SpringBoot自動配置原理</font>這節標【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】,調用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

// 獲取符合條件的自動配置類,避免加載沒必要要的自動配置類從而形成內存浪費
protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    // 獲取是否有配置spring.boot.enableautoconfiguration屬性,默認返回true
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 得到@Congiguration標註的Configuration類即被審視introspectedClass的註解數據,
    // 好比:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 將會獲取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的註解數據
    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的註解數據
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 檢查要被排除的配置類,由於有些不是自動配置類,故要拋出異常
    checkExcludedClasses(configurations, exclusions);
    // 【2】將要排除的配置類移除
    configurations.removeAll(exclusions);
    // 【3】由於從spring.factories文件獲取的自動配置類太多,若是有些沒必要要的自動配置類都加載進內存,會形成內存浪費,所以這裏須要進行過濾
    // 注意這裏會調用AutoConfigurationImportFilter的match方法來判斷是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,後面會重點分析一下
    configurations = filter(configurations, autoConfigurationMetadata);
    // 【4】獲取了符合條件的自動配置類後,此時觸發AutoConfigurationImportEvent事件,
    // 目的是告訴ConditionEvaluationReport條件評估報告器對象來記錄符合條件的自動配置類
    // 該事件何時會被觸發?--> 在刷新容器時調用invokeBeanFactoryPostProcessors後置處理器時觸發
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 【5】將符合條件和要排除的自動配置類封裝進AutoConfigurationEntry對象,並返回
    return new AutoConfigurationEntry(configurations, exclusions); 
}

AutoConfigurationEntry方法主要作的事情就是獲取符合條件的自動配置類,避免加載沒必要要的自動配置類從而形成內存浪費。咱們下面總結下AutoConfigurationEntry方法主要作的事情:

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

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

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

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

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

SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)
<center>圖3</center>

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

// AutoConfigurationImportSelector.java

private List<String> filter(List<String> configurations,
            AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    // 將從spring.factories中獲取的自動配置類轉出字符串數組
    String[] candidates = StringUtils.toStringArray(configurations);
    // 定義skip數組,是否須要跳過。注意skip數組與candidates數組順序一一對應
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    // getAutoConfigurationImportFilters方法:拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
    // 而後遍歷這三個條件類去過濾從spring.factories加載的大量配置類
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // 調用各類aware方法,將beanClassLoader,beanFactory等注入到filter對象中,
        // 這裏的filter對象即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
        invokeAwareMethods(filter);
        // 判斷各類filter來判斷每一個candidate(這裏實質要經過candidate(自動配置類)拿到其標註的
        // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication裏面的註解值)是否匹配,
        // 注意candidates數組與match數組一一對應
        /**********************【主線,重點關注】********************************/
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        // 遍歷match數組,注意match順序跟candidates的自動配置類一一對應
        for (int i = 0; i < match.length; i++) {
            // 如有不匹配的話
            if (!match[i]) {
                // 不匹配的將記錄在skip數組,標誌skip[i]爲true,也與candidates數組一一對應
                skip[i] = true;
                // 由於不匹配,將相應的自動配置類置空
                candidates[i] = null;
                // 標註skipped爲true
                skipped = true; 
            }
        }
    } 
    // 這裏表示若全部自動配置類通過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方法主要作的事情就是調用AutoConfigurationImportFilter接口的match方法來判斷每個自動配置類上的條件註解(如有的話)@ConditionalOnClass,@ConditionalOnBean@ConditionalOnWebApplication是否知足條件,若知足,則返回true,說明匹配,若不知足,則返回false說明不匹配。

咱們如今知道AutoConfigurationImportSelectorfilter方法主要作了什麼事情就好了,如今先不用研究的過深,至於AutoConfigurationImportFilter接口的match方法將在<font color=Blue>5 AutoConfigurationImportFilter</font>這小節再詳細分析,填補一下咱們前一篇條件註解源碼分析中留下的坑。

注意:咱們堅持主線優先的原則,其餘枝節代碼這裏不深究,以避免丟了主線哈。

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

這裏繼續深究前面<font color=Blue> 4 分析SpringBoot自動配置原理</font>這節標【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());
    // 這裏獲得通過濾後全部符合條件的自動配置類的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方法主要是針對通過排除掉exclude的和被AutoConfigurationImportFilter接口過濾後的知足條件的自動配置類再進一步排除exclude的自動配置類,而後再排序。邏輯很簡單,再也不詳述。

不過有個疑問,前面已經exclude過一次了,爲什麼這裏還要再exclude一次?

5 AutoConfigurationImportFilter

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

所以咱們繼續分析AutoConfigurationImportFilter接口,分析其match方法,同時也是對前一篇@ConditionalOnXxx的源碼分析文章中留下的坑進行填補。

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

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

一樣,在分析AutoConfigurationImportFilter接口的match方法前,咱們先來看下其類關係圖:

SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)
<center>圖4</center>

能夠看到,AutoConfigurationImportFilter接口有一個具體的實現類FilteringSpringBootConditionFilteringSpringBootCondition又有三個具體的子類:OnClassCondition,OnBeanCondtitionOnWebApplicationCondition

那麼這幾個類之間的關係是怎樣的呢?

FilteringSpringBootCondition實現了AutoConfigurationImportFilter接口的match方法,而後在FilteringSpringBootConditionmatch方法調用getOutcomes這個抽象模板方法返回自動配置類的匹配與否的信息。同時,最重要的是FilteringSpringBootCondition的三個子類OnClassCondition,OnBeanCondtitionOnWebApplicationCondition將會複寫這個模板方法實現本身的匹配判斷邏輯。

好了,AutoConfigurationImportFilter接口的總體關係已經清楚了,如今咱們再進入其具體實現類FilteringSpringBootConditionmatch方法看看是其如何根據條件過濾自動配置類的。

// FilteringSpringBootCondition.java

@Override
public boolean[] match(String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    // 建立評估報告
    ConditionEvaluationReport report = ConditionEvaluationReport
            .find(this.beanFactory);
    // 注意getOutcomes是模板方法,將spring.factories文件種加載的全部自動配置類傳入
    // 子類(這裏指的是OnClassCondition,OnBeanCondition和OnWebApplicationCondition類)去過濾
    // 注意outcomes數組存儲的是不匹配的結果,跟autoConfigurationClasses數組一一對應
    /*****************************【主線,重點關注】*********************************************/
    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) {
            // 這裏如有某個類不匹配的話,此時調用父類SpringBootCondition的logOutcome方法打印日誌
            logOutcome(autoConfigurationClasses[i], outcomes[i]);
            // 並將不匹配狀況記錄到report
            if (report != null) {
                report.recordConditionEvaluation(autoConfigurationClasses[i], this,
                        outcomes[i]);
            }
        }
    }
    return match;
}

FilteringSpringBootConditionmatch方法主要作的事情仍是調用抽象模板方法getOutcomes來根據條件來過濾自動配置類,而複寫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
    // 這裏通過測試用兩個線程去跑的話性能是最好的,大於兩個線程性能反而變差
    int split = autoConfigurationClasses.length / 2;
    // 【1】開啓一個新線程去掃描判斷已經加載的一半自動配置類
    OutcomesResolver firstHalfResolver = createOutcomesResolver(
            autoConfigurationClasses, 0, split, autoConfigurationMetadata);
    // 【2】這裏用主線程去掃描判斷已經加載的一半自動配置類
    OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
            autoConfigurationClasses, split, autoConfigurationClasses.length,
            autoConfigurationMetadata, getBeanClassLoader());
    // 【3】先讓主線程去執行解析一半自動配置類是否匹配條件
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
    // 【4】這裏用新開啓的線程取解析另外一半自動配置類是否匹配
    // 注意爲了防止主線程執行過快結束,resolveOutcomes方法裏面調用了thread.join()來
    // 讓主線程等待新線程執行結束,由於後面要合併兩個線程的解析結果
    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
    // 新建一個ConditionOutcome數組來存儲自動配置類的篩選結果
    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    // 將前面兩個線程的篩選結果分別拷貝進outcomes數組
    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
    // 返回自動配置類的篩選結果
    return outcomes;
}

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

因爲解析自動配置類是否匹配比較耗時,所以從上面代碼中咱們能夠看到分別建立了firstHalfResolversecondHalfResolver兩個解析對象,這兩個解析對象個分別對應一個線程去解析加載的自動配置類是否符合條件,最終將兩個線程的解析自動配置類的匹配結果合併後返回。

那麼自動配置類是否符合條件的解析判斷過程又是怎樣的呢?如今咱們分別來看一下上面代碼註釋標註的【1】【2】【3】【4】處。

5.1.1 createOutcomesResolver

這裏對應前面<font color=blue>5.1節</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對象做爲構造器參數傳入
        return new ThreadedOutcomesResolver(outcomesResolver);
    }
    // 若上面開啓的線程拋出AccessControlException異常,則返回StandardOutcomesResolver對象
    catch (AccessControlException ex) {
        return outcomesResolver;
    }
}

能夠看到createOutcomesResolver方法建立了一個封裝了StandardOutcomesResolver類的ThreadedOutcomesResolver解析對象。
咱們再來看下ThreadedOutcomesResolver這個線程解析類封裝StandardOutcomesResolver這個對象的目的是什麼?咱們繼續跟進代碼:

// OnClassCondtion.java

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

能夠看到在構造ThreadedOutcomesResolver對象時候,原來是開啓了一個線程,而後這個線程其實仍是調用了剛傳進來的StandardOutcomesResolver對象的resolveOutcomes方法去解析自動配置類。具體如何解析呢?稍後咱們在分析【3】處代碼secondHalfResolver.resolveOutcomes();的時候再深究。

5.1.2 new StandardOutcomesResolver

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

5.1.3 StandardOutcomesResolver.resolveOutcomes方法

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

這裏StandardOutcomesResolver.resolveOutcomes方法承擔瞭解析自動配置類匹配與否的所有邏輯,是咱們要重點分析的方法,resolveOutcomes方法最終把解析的自動配置類的結果賦給secondHalf數組。那麼它是如何解析自動配置類是否匹配條件的呢?

// OnClassCondition$StandardOutcomesResolver.java

public ConditionOutcome[] resolveOutcomes() {
    // 再調用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註解且有值,此時調用getOutcome判斷是否存在於類路徑中
            if (candidates != null) {
                // 拿到自動配置類註解@ConditionalOnClass的值後,再調用getOutcome方法去判斷匹配結果,若該類存在於類路徑,則getOutcome返回null,不然非null
                /*******************【主線,重點關注】******************/
                outcomes[i - start] = getOutcome(candidates);
            }
        }
    }
    return outcomes;
}

能夠看到StandardOutcomesResolver.resolveOutcomes的方法中再次調用getOutcomes方法,主要是從autoConfigurationMetadata對象中獲取到自動配置類上的註解@ConditionalOnClass指定的類的全限定名,而後做爲參數傳入getOutcome方法用於去類路徑加載該類,若能加載到則說明註解@ConditionalOnClass知足條件,此時說明自動配置類匹配成功。

可是別忘了,這裏只是過了@ConditionalOnClass註解這一關,若自動配置類還有其餘註解好比@ConditionalOnBean,若該@ConditionalOnBean註解不知足條件的話,一樣最終結果是不匹配的。這裏扯的有點遠,咱們回到OnClassCondtion的判斷邏輯,繼續進入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的值只有一個的話,直接調用getOutcome方法判斷是否匹配
        if (!candidates.contains(",")) {
            // 看到由於傳入的參數是 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方法再次調用重載方法getOutcome進一步去判斷註解@ConditionalOnClass指定的類存不存在類路徑中,跟着主線繼續跟進去:

// OnClassCondition$StandardOutcomesResolver.java

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

咱們一層一層的剝,最終剝到了最底層了,這個真的須要足夠耐心,沒辦法,源碼只能一點一點的啃,嘿嘿。能夠看到最終是調用ClassNameFiltermatches方法來判斷@ConditionalOnClass指定的類存不存在類路徑中,若不存在的話,則返回不匹配。

咱們繼續跟進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枚舉類實現
    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的一個內部枚舉類,其實現了判斷指定類是否存在於classpath中的邏輯,這個類很簡單,這裏再也不詳述。

5.1.4 ThreadedOutcomesResolver.resolveOutcomes方法

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

前面分析<font color=blue>5.1.3 StandardOutcomesResolver.resolveOutcomes</font>方法已經刨根追底,陷入細節比較深,如今咱們須要跳出來繼續看前面標註的【4】的代碼ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes()的方法哈。

這裏是用新開啓的線程去調用StandardOutcomesResolver.resolveOutcomes方法解析另外一半自動配置類是否匹配,由於是新線程,這裏極可能會出現這麼一種狀況:主線程解析完屬於本身解析的一半自動配置類後,那麼久繼續往下跑了,此時不會等待新開啓的子線程的。

所以,爲了讓主線程解析完後,咱們須要讓主線程繼續等待正在解析的子線程,直到子線程結束。那麼咱們繼續跟進代碼區看下ThreadedOutcomesResolver.resolveOutcomes方法是怎樣實現讓主線程等待子線程的:

// OnClassCondition$ThreadedOutcomesResolver.java

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

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

5.2 OnBeanCondition和OnWebApplicationCondition

前面<font color=blue>5.1 OnClassCondition</font>節深刻分析了OnClassCondition是如何過濾自動配置類的,那麼自動配置類除了要通過OnClassCondition的過濾,還要通過OnBeanConditionOnWebApplicationCondition這兩個條件類的過濾,這裏再也不詳述,有興趣的小夥伴可自行分析。

6 AutoConfigurationImportListener

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

咱們直接點進fireAutoConfigurationImportEvents方法看看其是如何觸發事件的:

// 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) {
            // 這裏調用各類Aware方法用於觸發事件前賦值,好比設置factory,environment等
            invokeAwareMethods(listener);
            // 真正觸發AutoConfigurationImportEvent事件即回調listener的onXXXEveent方法。這裏用於記錄自動配置類的評估信息
            listener.onAutoConfigurationImportEvent(event); 
        }
    }
}

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

  1. 調用getAutoConfigurationImportListeners方法從spring.factoris配置文件獲取實現AutoConfigurationImportListener接口的事件監聽器;以下圖,能夠看到獲取的是ConditionEvaluationReportAutoConfigurationImportListener

SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)

  1. 遍歷獲取的各個事件監聽器,而後調用監聽器各類Aware方法給監聽器賦值,最後再依次回調事件監聽器的onAutoConfigurationImportEvent方法,執行監聽事件的邏輯。

此時咱們再來看下ConditionEvaluationReportAutoConfigurationImportListener監聽器監聽到事件後,它的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監聽器監聽到事件後,作的事情很簡單,只是分別記錄下符合條件和被exclude的自動配置類。

7 AutoConfigurationPackages

前面已經詳述了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的靜態內部類,實現了ImportBeanDefinitionRegistrarDeterminableImports兩個接口。如今咱們主要來關注下Registrar實現的registerBeanDefinitions方法,顧名思義,這個方法是註冊bean定義的方法。看到它又調用了AutoConfigurationPackagesregister方法,繼續跟進源碼:

// 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的目的是爲了什麼呢?
結合官網註釋知道,註冊這個自動配置包名相關的bean是爲了被其餘地方引用,好比JPA entity scanner,具體拿來幹什麼久不知道了,這裏再也不深究了。

8 小結

好了,SpringBoot的自動配置的源碼分析就到這裏了,比較長,有些地方也很深刻細節,讀完須要必定的耐心。

最後,咱們再總結下SpringBoot自動配置的原理,主要作了如下事情:

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

最後留個本身的疑問,還望知道答案的大佬解答,這裏表示感謝

爲了不加載沒必要要的自動配置類形成內存浪費,FilteringSpringBootCondition用於過濾spring.factories文件的自動配置類,而FilteringSpringBootCondition爲啥只有OnOnBeanCondition,OnClassConditiononWebApplicationCondition這三個條件類用於過濾,爲啥沒有onPropertyCondtiononResourceCondition等條件類來過濾自動配置類呢?

下節預告:
<font color=Blue>SpringBoot的啓動流程是怎樣的?--SpringBoot源碼(五)</font>

原創不易,幫忙點個讚唄!

因爲筆者水平有限,若文中有錯誤還請指出,謝謝。

參考:

1,@AutoConfigurationPackage註解


歡迎關注【源碼筆記】公衆號,一塊兒學習交流。

SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)

相關文章
相關標籤/搜索