天天看點

SpringBoot是如何實作自動配置的?--SpringBoot源碼(四)1 前言2 @SpringBootApplication注解3 如何去找SpringBoot自動配置實作邏輯的入口方法?4 分析SpringBoot自動配置原理5 AutoConfigurationImportFilter6 AutoConfigurationImportListener7 AutoConfigurationPackages8 小結

注:該源碼分析對應SpringBoot版本為2.1.0.RELEASE

1 前言

本篇接

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

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

  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

這個注解有關,我們先來看下其這個注解的源碼:

@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

來決定是否導入某個配置類)。

是以,可以看出

AutoConfigurationImportSelector

類是我們本篇的重點,因為SpringBoot的自動配置肯定有一個配置類,而這個配置類的導入則需要靠

AutoConfigurationImportSelector

這個哥們來實作。

接下來我們重點來看

AutoConfigurationImportSelector

這個類,完了我們再簡單分析下

@AutoConfigurationPackage

這個注解的邏輯。

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

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

在找SpringBoot自動配置實作邏輯的入口方法前,我們先來看下

AutoConfigurationImportSelector

的相關類圖,好有個整體的了解。看下圖:

圖1

可以看到

AutoConfigurationImportSelector

重點是實作了

DeferredImportSelector

接口和各種

Aware

接口,然後

DeferredImportSelector

接口又繼承了

ImportSelector

自然而然的,我們會去關注

AutoConfigurationImportSelector

複寫

DeferredImportSelector

接口的實作方法

selectImports

方法,因為

selectImports

方法跟導入自動配置類有關,而這個方法往往是程式執行的入口方法。經過調試發現

selectImports

方法很具有迷惑性,

selectImports

方法跟自動配置相關的邏輯有點關系,但實質關系不大。

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

最簡單的方法就是在

AutoConfigurationImportSelector

類的每個方法都打上斷點,然後調試看先執行到哪個方法。但是我們可以不這麼做,我們回想下,自定義一個

Starter

的時候我們是不是要在

spring.factories

配置檔案中配置

EnableAutoConfiguration=XxxAutoConfiguration           

是以可以推斷,SpringBoot的自動配置原理肯定跟從

spring.factories

配置檔案中加載自動配置類有關,于是結合

AutoConfigurationImportSelector

的方法注釋,我們找到了

getAutoConfigurationEntry

方法。于是我們在這個方法裡面打上一個斷點,此時通過調用棧幀來看下更上層的入口方法在哪裡,然後我們再從跟自動配置相關的更上層的入口方法開始分析。

圖2

通過圖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】

處的的代碼是我們分析的重中之重,自動配置的相關的絕大部分邏輯全在這裡了,将在4.1 分析自動配置的主要邏輯深入分析。那麼`this.group.process(deferredImport.getConfigurationClass().getMetadata(),

deferredImport.getImportSelector())`;主要做的事情就是在`this.group`即`AutoConfigurationGroup`對象的`process`方法中,傳入的`AutoConfigurationImportSelector`對象來選擇一些符合條件的自動配置類,過濾掉一些不符合條件的自動配置類,就是這麼個事情,無他。

                 

注:

  1. AutoConfigurationGroup

    :是

    AutoConfigurationImportSelector

    的内部類,主要用來處理自動配置相關的邏輯,擁有

    process

    selectImports

    方法,然後擁有

    entries

    autoConfigurationEntries

    集合屬性,這兩個集合分别存儲被處理後的符合條件的自動配置類,我們知道這些就足夠了;
  2. AutoConfigurationImportSelector

    :承擔自動配置的絕大部分邏輯,負責選擇一些符合條件的自動配置類;
  3. metadata

    :标注在SpringBoot啟動類上的

    @SpringBootApplication

    注解中繼資料

    【2】

    this.group.selectImports

    的方法主要是針對前面的

    process

    方法處理後的自動配置類再進一步有選擇的選擇導入,将在4.2 有選擇的導入自動配置類這小節深入分析。

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

這裡繼續深究前面 4 分析SpringBoot自動配置原理這節标

【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

條件評估報告器對象來記錄符合條件的自動配置類;(這個在6 AutoConfigurationImportListener這小節詳細分析。)

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

圖3

總結了

AutoConfigurationEntry

方法主要的邏輯後,我們再來細看一下

AutoConfigurationImportSelector

filter

方法:

// 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);
}           

AutoConfigurationImportSelector

filter

方法主要做的事情就是調用

AutoConfigurationImportFilter

接口的

match

方法來判斷每一個自動配置類上的條件注解(若有的話)

@ConditionalOnClass

,

@ConditionalOnBean

@ConditionalOnWebApplication

是否滿足條件,若滿足,則傳回true,說明比對,若不滿足,則傳回false說明不比對。

我們現在知道

AutoConfigurationImportSelector

filter

方法主要做了什麼事情就行了,現在先不用研究的過深,至于

AutoConfigurationImportFilter

match

方法将在5 AutoConfigurationImportFilter這小節再詳細分析,填補一下我們前一篇條件注解源碼分析中留下的坑。

注意:我們堅持主線優先的原則,其他枝節代碼這裡不深究,以免丢了主線哈。

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

【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

這裡繼續深究前面 4.1節的

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

方法前,我們先來看下其類關系圖:

圖4

可以看到,

AutoConfigurationImportFilter

接口有一個具體的實作類

FilteringSpringBootCondition

FilteringSpringBootCondition

又有三個具體的子類:

OnClassCondition

OnBeanCondtition

OnWebApplicationCondition

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

FilteringSpringBootCondition

實作了

AutoConfigurationImportFilter

match

方法,然後在

FilteringSpringBootCondition

match

方法調用

getOutcomes

這個抽象模闆方法傳回自動配置類的比對與否的資訊。同時,最重要的是

FilteringSpringBootCondition

的三個子類

OnClassCondition

OnBeanCondtition

OnWebApplicationCondition

将會複寫這個模闆方法實作自己的比對判斷邏輯。

好了,

AutoConfigurationImportFilter

接口的整體關系已經清楚了,現在我們再進入其具體實作類

FilteringSpringBootCondition

match

方法看看是其如何根據條件過濾自動配置類的。

// 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;
}           

FilteringSpringBootCondition

match

方法主要做的事情還是調用抽象模闆方法

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;
}           

OnClassCondition

getOutcomes

方法主要解析自動配置類是否符合比對條件,當然這個比對條件指自動配置類上的注解

@ConditionalOnClass

指定的類存不存在于

classpath

中,存在則傳回比對,不存在則傳回不比對。

由于解析自動配置類是否比對比較耗時,是以從上面代碼中我們可以看到分别建立了

firstHalfResolver

secondHalfResolver

兩個解析對象,這兩個解析對象個分别對應一個線程去解析加載的自動配置類是否符合條件,最終将兩個線程的解析自動配置類的比對結果合并後傳回。

那麼自動配置類是否符合條件的解析判斷過程又是怎樣的呢?現在我們分别來看一下上面代碼注釋标注的

【1】

【2】

【3】

【4】

處。

5.1.1 createOutcomesResolver

這裡對應前面5.1節的代碼注釋标注

【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

這裡對應前面5.1節的

【2】

處的代碼

OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(...);

,邏輯很簡單,就是建立了一個

StandardOutcomesResolver

對象,用于後面解析自動配置類是否比對,同時,建立的一個線程也是利用它來完成自動配置類的解析的。

5.1.3 StandardOutcomesResolver.resolveOutcomes方法

這裡對應前面5.1節标注的

【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;
}           

我們一層一層的剝,最終剝到了最底層了,這個真的需要足夠耐心,沒辦法,源碼隻能一點一點的啃,嘿嘿。可以看到最終是調用

ClassNameFilter

matches

方法來判斷

@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方法

這裡對應前面5.1節的标注的

【4】

ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes()

前面分析5.1.3 StandardOutcomesResolver.resolveOutcomes方法已經刨根追底,陷入細節比較深,現在我們需要跳出來繼續看前面标注的

【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

前面5.1 OnClassCondition節深入分析了

OnClassCondition

是如何過濾自動配置類的,那麼自動配置類除了要經過

OnClassCondition

的過濾,還要經過

OnBeanCondition

OnWebApplicationCondition

這兩個條件類的過濾,這裡不再詳述,有興趣的小夥伴可自行分析。

6 AutoConfigurationImportListener

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

  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

的靜态内部類,實作了

ImportBeanDefinitionRegistrar

DeterminableImports

兩個接口。現在我們主要來關注下

Registrar

實作的

registerBeanDefinitions

方法,顧名思義,這個方法是注冊

bean

定義的方法。看到它又調用了

AutoConfigurationPackages

register

方法,繼續跟進源碼:

// 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

OnClassCondition

onWebApplicationCondition

這三個條件類用于過濾,為啥沒有

onPropertyCondtion

onResourceCondition

等條件類來過濾自動配置類呢?

下節預告:

SpringBoot的啟動流程是怎樣的?--SpringBoot源碼(五)

原創不易,幫忙點個贊呗!

由于筆者水準有限,若文中有錯誤還請指出,謝謝。

參考:

1,

@AutoConfigurationPackage注解

歡迎關注【源碼筆記】公衆号,一起學習交流。