您好,登錄后才能下訂單哦!
1 前言
本篇接
助力SpringBoot自動配置的條件注解ConditionalOnXXX分析--SpringBoot源碼(三)
溫故而知新,我們來簡單回顧一下上篇的內容,上一篇我們分析了SpringBoot的條件注解@ConditionalOnXxx的相關源碼,現挑重點總結如下:
@ConditionalOnXxx
的條件類OnXxxCondition
都是繼承于SpringBootCondition
基類,而SpringBootCondition
又實現了Condition
接口。SpringBootCondition
基類主要用來打印一些條件注解評估報告的日志,這些條件評估信息全部來源于其子類注解條件類OnXxxCondition
,因此其也抽象了一個模板方法getMatchOutcome
留給子類去實現來評估其條件注解是否符合條件。AutoConfigurationImportFilter
接口,這篇文章我們來填一下這個坑。前面我們分析了跟SpringBoot的自動配置息息相關內置條件注解@ConditionalOnXxx
后,現在我們就開始來擼SpringBoot自動配置的相關源碼了。
在開始前,我們先想一下,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
這個注解的邏輯。
可以肯定的是SpringBoot的自動配置的邏輯肯定與AutoConfigurationImportSelector這個類有關,那么我們該如何去找到SpringBoot自動配置實現邏輯的入口方法呢?
在找SpringBoot自動配置實現邏輯的入口方法前,我們先來看下AutoConfigurationImportSelector
的相關類圖,好有個整體的理解。看下圖:
<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
方法。于是我們在這個方法里面打上一個斷點,此時通過調用棧幀來看下更上層的入口方法在哪里,然后我們再從跟自動配置相關的更上層的入口方法開始分析。
<center>圖2</center>
通過圖1我們可以看到,跟自動配置邏輯相關的入口方法在DeferredImportSelectorGrouping
類的getImports
方法處,因此我們就從DeferredImportSelectorGrouping
類的getImports
方法來開始分析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.group
即AutoConfigurationGroup
對象的process
方法中,傳入的AutoConfigurationImportSelector
對象來選擇一些符合條件的自動配置類,過濾掉一些不符合條件的自動配置類,就是這么個事情,無他。
注:
AutoConfigurationGroup
:是AutoConfigurationImportSelector
的內部類,主要用來處理自動配置相關的邏輯,擁有process
和selectImports
方法,然后擁有entries
和autoConfigurationEntries
集合屬性,這兩個集合分別存儲被處理后的符合條件的自動配置類,我們知道這些就足夠了;AutoConfigurationImportSelector
:承擔自動配置的絕大部分邏輯,負責選擇一些符合條件的自動配置類;metadata
:標注在SpringBoot啟動類上的@SpringBootApplication
注解元數據標【2】
的this.group.selectImports
的方法主要是針對前面的process
方法處理后的自動配置類再進一步有選擇的選擇導入,將在<font color=blue>4.2 有選擇的導入自動配置類</font>這小節深入分析。
這里繼續深究前面<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】 最后再將符合條件的自動配置類返回。
<center>圖3</center>
總結了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
方法將在<font color=Blue>5 AutoConfigurationImportFilter</font>這小節再詳細分析,填補一下我們前一篇條件注解源碼分析中留下的坑。
注意:我們堅持主線優先的原則,其他枝節代碼這里不深究,以免丟了主線哈。
這里繼續深究前面<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
一次?
這里繼續深究前面<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
方法前,我們先來看下其類關系圖:
<center>圖4</center>
可以看到,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
方法進行分析。
先直接上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】
處。
這里對應前面<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();
的時候再深究。
這里對應前面<font color=blue>5.1節</font>的【2】
處的代碼OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(...);
,邏輯很簡單,就是創建了一個StandardOutcomesResolver
對象,用于后面解析自動配置類是否匹配,同時,新建的一個線程也是利用它來完成自動配置類的解析的。
這里對應前面<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;
}
我們一層一層的剝,最終剝到了最底層了,這個真的需要足夠耐心,沒辦法,源碼只能一點一點的啃,嘿嘿。可以看到最終是調用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
中的邏輯,這個類很簡單,這里不再詳述。
這里對應前面<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
數組。
前面<font color=blue>5.1 OnClassCondition</font>節深入分析了OnClassCondition
是如何過濾自動配置類的,那么自動配置類除了要經過OnClassCondition
的過濾,還要經過OnBeanCondition
和OnWebApplicationCondition
這兩個條件類的過濾,這里不再詳述,有興趣的小伙伴可自行分析。
這里繼續深究前面<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
方法做了以下兩件事情:
getAutoConfigurationImportListeners
方法從spring.factoris
配置文件獲取實現AutoConfigurationImportListener
接口的事件監聽器;如下圖,可以看到獲取的是ConditionEvaluationReportAutoConfigurationImportListener
: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
的自動配置類。
前面已經詳述了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
,具體拿來干什么久不知道了,這里不再深究了。
好了,SpringBoot的自動配置的源碼分析就到這里了,比較長,有些地方也很深入細節,讀完需要一定的耐心。
最后,我們再總結下SpringBoot自動配置的原理,主要做了以下事情:
@EnableAutoConfiguration
注解的exclude
屬性指定的自動配置類;AutoConfigurationImportFilter
接口去過濾自動配置類是否符合其標注注解(若有標注的話)@ConditionalOnClass
,@ConditionalOnBean
和@ConditionalOnWebApplication
的條件,若都符合的話則返回匹配結果;AutoConfigurationImportEvent
事件,告訴ConditionEvaluationReport
條件評估報告器對象來分別記錄符合條件和exclude
的自動配置類。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。