《Spring Boot 源碼學習系列》
引言
上篇博文,筆者帶大家了解了自動裝配流程中有關自動配置加載的流程;
本篇將介紹自動裝配流程剩余的內(nèi)容,包含了自動配置組件的排除和過濾、觸發(fā)自動配置事件。
往期內(nèi)容
在開始本篇的內(nèi)容介紹之前,我們先來看看往期的系列文章【有需要的朋友,歡迎關注系列專欄】:
Spring Boot 源碼學習 |
Spring Boot 項目介紹 |
Spring Boot 核心運行原理介紹 |
【Spring Boot 源碼學習】@EnableAutoConfiguration 注解 |
【Spring Boot 源碼學習】@SpringBootApplication 注解 |
【Spring Boot 源碼學習】走近 AutoConfigurationImportSelector |
【Spring Boot 源碼學習】自動裝配流程源碼解析(上) |
主要內(nèi)容
書接上篇,本篇繼續(xù)從源碼分析自動裝配流程:
4. 排除指定自動配置組件
如果我們在實際使用時,并不需要其中的某些組件,那就可以通過 @EnableAutoConfiguration
注解的 exclude
或 excludeName
屬性來進行有針對性的排除 或者 在Spring Boot 的配置文件進行排除。
下面我們來分析一下排除邏輯的源碼:
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
// 獲取 exclude 屬性 配置的 待排除的自動配置組件
excluded.addAll(asList(attributes, "exclude"));
// 獲取 excludeName 屬性 配置的 待排除的自動配置組件
excluded.addAll(asList(attributes, "excludeName"));
// 獲取 Spring Boot 配置文件中 配置的 待排除的自動配置組件
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
protected List<String> getExcludeAutoConfigurationsProperty() {
Environment environment = getEnvironment();
if (environment == null) {
return Collections.emptyList();
}
if (environment instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(environment);
return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
.map(Arrays::asList)
.orElse(Collections.emptyList());
}
String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
上面的代碼也挺好理解,分別從注解屬性 exclude 、 excludeName 以及配置文件中獲取待排除的自動配置組件。
下面我們來演示一下該如何配置,從而排除我們不需要的自動配置組件:
- 添加注解屬性 exclude 和 excludeName
- 添加配置文件屬性
- 我們啟動先前建的 Spring Boot 項目的應用類,分別查看到如下的信息:
當上面獲取了被排除的自動配置組件之后,需要對待排除的類進行檢查,如下所示:
checkExcludedClasses(configurations, exclusions);
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
for (String exclusion : exclusions) {
// 如果待排除的自動配置類存在且可以加載
// 并且已去重過的自動配置組件中不存在該待排除的自動配置類
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
// 添加到非法的排除列表中
invalidExcludes.add(exclusion);
}
}
// 如果存在非法的排除項,則拋出相應的異常信息
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
for (String exclude : invalidExcludes) {
message.append("\t- ").append(exclude).append(String.format("%n"));
}
throw new IllegalStateException(String.format(
"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
message));
}
上述代碼中對于待排除類的檢查邏輯也好理解,如果待排除的自動配置類存在且可以加載【即存在于當前的ClassLoader中】,并且已去重過的自動配置組件中不存在該待排除的自動配置類,則認為待排除的自動配置類是非法的,拋出相關異常。
我們下面通過示例來驗證一下:
-
在我們的示例項目中添加一個自動配置類【注意這里只做演示,無其他意義】
-
配置文件添加項目中的一個自動配置類
-
我們啟動先前建的 Spring Boot 項目的應用類,可以看到如下的啟動異常報錯:
如果上述檢查通過,則說明待排除的自動配置類都符合要求,則調(diào)用如下代碼從自動配置集合中移除上面獲取的待排除的自動配置類信息。
configurations.removeAll(exclusions);
5. 過濾自動配置組件
經(jīng)過上面的自動配置組件排除邏輯之后,接下來就要過濾自動配置組件了,而過濾邏輯主要是通過檢查配置類的注解是否符合 spring.factories
文件中 AutoConfigurationImportFilter
指定的注解檢查條件,來決定該過濾哪些自動配置組件。
下面開始分析相關代碼,如下所示【Spring Boot 2.7.9】:
configurations = getConfigurationClassFilter().filter(configurations);
進入 getConfigurationClassFilter
方法,如下所示:
private ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}
getConfigurationClassFilter
方法返回一個 ConfigurationClassFilter
實例,用來過濾掉不必要的配置類。
繼續(xù)看 getAutoConfigurationImportFilters
方法,如下所示:
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
它通過 SpringFactoriesLoader
類的 loadFactories
方法來獲取 META-INF/spring.factories
中配置 key
為 AutoConfigurationImportFilter
的 Filters
列表;
我們可以查看相關配置了解一下,如下所示:
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
如上所示,在 spring-boot-autoconfigure
中默認配置了三個篩選條件:OnBeanCondition
、OnClassCondition
、OnWebApplicationCondition
,它們均實現(xiàn)了 AutoConfigurationImportFilter
接口。
相關類圖如下所示:
我們繼續(xù)往下看 invokeAwareMethods,如下所示:
private void invokeAwareMethods(Object instance) {
if (instance instanceof Aware) {
if (instance instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
}
if (instance instanceof BeanFactoryAware) {
((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
}
if (instance instanceof EnvironmentAware) {
((EnvironmentAware) instance).setEnvironment(this.environment);
}
if (instance instanceof ResourceLoaderAware) {
((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
}
}
}
這里先判斷傳入的 instance
對象是否是 Aware
接口?
如果是 Aware
接口,則判斷是否是它的 BeanClassLoaderAware
、 BeanFactoryAware
、EnvironmentAware
和 ResourceLoaderAware
這 4 個子接口實現(xiàn)?
如果是,則調(diào)用對應的回調(diào)方法設置相應參數(shù)。
Aware
接口是一個一個標記超接口,它表示一個bean
有資格通過回調(diào)方式從Spring
容器中接收特定框架對象的通知。具體的方法簽名由各個子接口確定,但通常應該只包括一個接受單個參數(shù)并返回void
的方法。
繼續(xù)往下翻看源碼,在 getConfigurationClassFilter
方法最后,我們可以看到它返回了一個內(nèi)部類 ConfigurationClassFilter
的實例對象。
有了內(nèi)部類 ConfigurationClassFilter
,接下來就可以開始自動配置組件的過濾操作,主要是通過內(nèi)部類 ConfigurationClassFilter
的 filter
方法來實現(xiàn)過濾自動配置組件的功能。
不過在分析 filter
方法之前,我們先了解下內(nèi)部類 ConfigurationClassFilter
中兩個成員變量 :
-
List<AutoConfigurationImportFilter> filters
: 上面已介紹,它是META-INF/spring.factories
中配置的 key 為AutoConfigurationImportFilter
的Filters
列表 -
AutoConfigurationMetadata autoConfigurationMetadata
:元數(shù)據(jù)文件META-INF/ spring-autoconfigure-metadata.properties
中配置對應實體類,詳細分析請看下面。
AutoConfigurationMetadata
自動配置元數(shù)據(jù),這個前面沒有涉及到,從內(nèi)部類 ConfigurationClassFilter
的構造函數(shù)中,我們可以看到如下:
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
詳細代碼,由于篇幅受限,這里就不貼了,大家可以自行查看相關源碼,從如下的截圖中,我們也可以直觀了解下。
好了,現(xiàn)在我們進入 filter
方法中,最關鍵的就是下面 的雙層 for 循環(huán)處理:
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
// 具體的過濾匹配操作
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
// 不符合過濾匹配要求,則清空當前的自動配置組件
candidates[i] = null;
skipped = true;
}
}
}
// 如果匹配完了,都無需跳過,直接返回當前配置即可
if (!skipped) {
return configurations;
}
// 有一個不滿足過濾匹配要求,都重新處理并返回符合要求的自動配置組件
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
// 如果當前自動配置組件不滿足過濾匹配要求,則上面會被清空
// 因此這里只需判斷即可獲取符合要求的自動配置組件
if (candidate != null) {
result.add(candidate);
}
}
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 result;
}
翻看上面的 filter
方法源碼,我們可以很明顯地看到,Spring Boot 就是通過如下的代碼來實現(xiàn)具體的過濾匹配操作。
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
在介紹如何實現(xiàn)具體的過濾匹配操作之前,先來看一下 AutoConfigurationImportFilter
接口的源碼:
@FunctionalInterface
public interface AutoConfigurationImportFilter {
boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}
上面的 match
方法就是實現(xiàn)具體的過濾匹配操作;
參數(shù):
-
String[] autoConfigurationClasses
:待過濾的自動配置類數(shù)組 -
AutoConfigurationMetadata autoConfigurationMetadata
:自動配置的元數(shù)據(jù)信息
返回值:
過濾匹配后的結果布爾數(shù)組,數(shù)組的大小與 autoConfigurationClasses
一致,如果自動配置組件需過濾掉,則設置布爾數(shù)組對應值為 false
。
結合上面的關聯(lián)類圖,我們可以看到 AutoConfigurationImportFilter
接口實際上是由抽象類 FilteringSpringBootCondition
來實現(xiàn)的,另外該抽象類還定義了一個抽象方法 getOutcomes
,然后 OnBeanCondition
、OnClassCondition
、OnWebApplicationCondition
繼承該抽象類,實現(xiàn) getOutcomes 方法,完成實際的過濾匹配操作。
抽象類 FilteringSpringBootCondition
的相關源碼如下【Spring Boot 2.7.9】:
abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
// 其他代碼省略
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
// 調(diào)用 由子類實現(xiàn)的 getOutcomes 方法,完成實際的過濾匹配操作
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
// 將 getOutcomes 方法返回結果轉換成布爾數(shù)組
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata);
// 其他代碼省略
}
通過上面源碼可以看出,抽象類 FilteringSpringBootCondition
的 match
方法主要是調(diào)用 getOutcomes
方法,并將其返回的結果轉換成布爾數(shù)組。而這個 getOutcomes
方法是過濾匹配的核心功能,由抽象類 FilteringSpringBootCondition
的子類來實現(xiàn)它。
有關 OnBeanCondition
、OnClassCondition
和 OnWebApplicationCondition
的內(nèi)容由于篇幅受限,后續(xù) Huazie 會再通過一篇博文詳細講解。
6. 觸發(fā)自動配置事件
經(jīng)過上面的排除和過濾之后,我們需要的自動配置類集合已經(jīng)可以返回了。不過在返回之前,還需要再進行最后一步,觸發(fā)自動配置導入事件,用來通知所有注冊的自動配置監(jiān)聽器進行相關處理。
fireAutoConfigurationImportEvents(configurations, exclusions);
進入 fireAutoConfigurationImportEvents
方法,可以看到如下源碼:
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}
接著,我們進入 getAutoConfigurationImportListeners
方法里,它是通過SpringFactoriesLoader
類提供的 loadFactories
方法將 spring.factories
中配置的接口 AutoConfigurationImportListener
的實現(xiàn)類加載出來。
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}
spring.factories
中配置的自動配置監(jiān)聽器,如下所示:
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
然后,將過濾出的自動配置類集合和被排除的自動配置類集合作為入?yún)?chuàng)建一個 AutoConfigurationImportEvent
事件對象;
其中
invokeAwareMethods(listener);
類似上面的invokeAwareMethods(filter);
這里不再贅述了。
最后,調(diào)用上述自動配置監(jiān)聽器的 onAutoConfigurationImportEvent
方法,并傳入上述獲取的 AutoConfigurationImportEvent
事件對象,來通知所有注冊的監(jiān)聽器進行相應的處理。
那這樣做有什么好處呢?
通過觸發(fā) AutoConfigurationImportEvent
事件,來通知所有注冊的監(jiān)聽器進行相應的處理,我們就可以在導入自動配置類之后,執(zhí)行一些附加的自定義邏輯或修改自動配置行為。文章來源:http://www.zghlxwxcb.cn/news/detail-680918.html
總結
本篇 Huazie 帶大家通讀了 Spring Boot 自動裝配邏輯的源碼,詳細分析了自動裝配的后續(xù)流程,主要包含 自動配置的排除 和 過濾。超過萬字,能夠看到這的小伙伴,Huazie 在這感謝各位的支持。后續(xù)我將持續(xù)輸出有關 Spring Boot 源碼學習系列的博文,想要及時了解更新的朋友,訂閱這里即可。文章來源地址http://www.zghlxwxcb.cn/news/detail-680918.html
到了這里,關于【Spring Boot 源碼學習】自動裝配流程源碼解析(下)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!