該系列文章是筆者在學習 Spring Boot 過程中總結下來的,裡面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼註釋 Spring Boot 原始碼分析 GitHub 地址 進行閱讀
Spring Boot 版本:2.2.x
最好對 Spring 原始碼有一定的瞭解,可以先檢視我的 《死磕 Spring 之 IoC 篇 - 文章導讀》 系列文章
如果該篇內容對您有幫助,麻煩點選一下“推薦”,也可以關注博主,感激不盡~
該系列其他文章請檢視:《精盡 Spring Boot 原始碼分析 - 文章導讀》
概述
在上一篇《剖析 @SpringBootApplication 註解》文章分析了 Spring Boot 的自動配置功能,在通過 @EnableAutoConfiguration
註解驅動整個自動配置模組的過程中,並不是所有的自動配置類都需要被注入,不同的自動配置類需要滿足一定條件後,才應該進行自動配置。
那麼 Spring Boot 怎麼知道滿足一定條件呢?Spring Boot 對 Spring 的 Condition 介面進行了擴充套件,然後結合自定義的註解,則可以判斷自動配置類是否符合條件。
例如 @ConditionalOnClass
可以指定必須存在哪些 Class 物件才注入這個 Bean。
那麼接下來,我們一起來看看 Spring 的 Condition 介面以及 Spring Boot 對其的擴充套件
Condition 演進史
Profile 的出場
在 Spring 3.1 的版本,為了滿足不同環境註冊不同的 Bean ,引入了 @Profile
註解。例如:
@Configuration
public class DataSourceConfiguration {
@Bean
@Profile("DEV")
public DataSource devDataSource() {
// ... 單機 MySQL
}
@Bean
@Profile("PROD")
public DataSource prodDataSource() {
// ... 叢集 MySQL
}
}
- 在測試環境下,我們註冊單機 MySQL 的 DataSource Bean
- 在生產環境下,我們註冊叢集 MySQL 的 DataSource Bean
Spring 3.1.x 的 @Profile
註解如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Profile {
/**
* The set of profiles for which this component should be registered.
*/
String[] value();
}
可以看到,最開始 @Profile
註解並沒有結合 @Conditional
註解一起使用,而是在後續版本才引入的
Condition 的出現
在 Spring 4.0 的版本,出現了 Condition 功能,體現在 org.springframework.context.annotation.Condition
介面,如下:
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
函式式介面,只有一個 matches(..)
方法,判斷是否匹配,從入參中可以知道,它是和註解配合一起實現 Condition 功能的,也就是 @Conditional
註解,如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
隨之 @Profile
註解也進行了修改,和 @Conditional
註解配合使用
Spring 5.1.x 的 @Profile
註解如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
這裡指定的的 Condition 實現類是 ProfileCondition,如下:
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
邏輯很簡答,從當前 Spring 應用上下文的 Environment 中判斷 @Profile
指定的環境是否被啟用,被啟用了表示匹配成功,則注入對應的 Bean,否則,不進行操作
但是 Spring 本身提供的 Condition 實現類不多,只有一個 ProfileCondition 物件
SpringBootCondition 的進擊
Spring Boot 為了滿足更加豐富的 Condition 場景,對 Spring 的 Condition 介面進行了擴充套件,提供更多的實現類,如下:
上面僅列出了部分 SpringBootCondition 的子類,同時這些子類與對應的註解配置一起使用
@ConditionalOnClass
:必須都存在指定的 Class 物件們@ConditionalOnMissingClass
:指定的 Class 物件們必須都不存在@ConditionalOnBean
:必須都存在指定的 Bean 們@ConditionalOnMissingBean
:指定的 Bean 們必須都不存在@ConditionalOnSingleCandidate
:必須存在指定的 Bean@ConditionalOnProperty
:指定的屬性是否有指定的值@ConditionalOnWebApplication
:當前的 WEB 應用型別是否在指定的範圍內(ANY、SERVLET、REACTIVE)@ConditionalOnNotWebApplication
:不是 WEB 應用型別
上面列出了 Spring Boot 中常見的幾種 @ConditionXxx
註解,他們都配合 @Conditional
註解與對應的 Condition 實現類一起使用,提供了非常豐富的 Condition 場景
Condition 在哪生效?
Spring 提供了 Condition 介面以及 @Conditional
註解,那麼在 Spring 中哪裡體現,或者說是哪裡進行判斷的呢?
其實我在 《死磕Spring之IoC篇 - @Bean 等註解的實現原理》 這篇文章中有提到過,我們稍微回顧一下,有兩種情況:
- 通過
@Component
註解(及派生註解)標註的 Bean @Configuration
標註的配置類中的@Bean
標註的方法 Bean
普通 Bean
第一種情況是在 Spring 掃描指定路徑下的 .class 檔案解析成對應的 BeanDefinition(Bean 的前身)時,會根據 @Conditional
註解判斷是否符合條件,如下:
// ClassPathBeanDefinitionScanner.java
public int scan(String... basePackages) {
// <1> 獲取掃描前的 BeanDefinition 數量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// <2> 進行掃描,將過濾出來的所有的 .class 檔案生成對應的 BeanDefinition 並註冊
doScan(basePackages);
// Register annotation config processors, if necessary.
// <3> 如果 `includeAnnotationConfig` 為 `true`(預設),則註冊幾個關於註解的 PostProcessor 處理器(關鍵)
// 在其他地方也會註冊,內部會進行判斷,已註冊的處理器不會再註冊
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// <4> 返回本次掃描註冊的 BeanDefinition 數量
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
// ClassPathScanningCandidateComponentProvider.java
private boolean isConditionMatch(MetadataReader metadataReader) {
if (this.conditionEvaluator == null) {
this.conditionEvaluator =
new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
}
return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
}
上面只是簡單的提一下,可以看到會通過 ConditionEvaluator 計算器進行計算,判斷是否滿足條件
配置類
第二種情況是 Spring 會對 配置類進行處理,掃描到帶有 @Bean
註解的方法,嘗試解析成 BeanDefinition(Bean 的前身)時,會根據 @Conditional
註解判斷是否符合條件,如下:
// ConfigurationClassBeanDefinitionReader.java
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// <1> 如果不符合 @Conditional 註解的條件,則跳過
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
// <2> 如果當前 ConfigurationClass 是通過 @Import 註解被匯入的
if (configClass.isImported()) {
// <2.1> 根據該 ConfigurationClass 物件生成一個 BeanDefinition 並註冊
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// <3> 遍歷當前 ConfigurationClass 中所有的 @Bean 註解標註的方法
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
// <3.1> 根據該 BeanMethod 物件生成一個 BeanDefinition 並註冊(注意這裡有無 static 修飾會有不同的配置)
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// <4> 對 @ImportResource 註解配置的資源進行處理,對裡面的配置進行解析並註冊 BeanDefinition
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// <5> 通過 @Import 註解匯入的 ImportBeanDefinitionRegistrar 實現類往 BeanDefinitionRegistry 註冊 BeanDefinition
// Mybatis 整合 Spring 就是基於這個實現的,可檢視 Mybatis-Spring 專案中的 MapperScannerRegistrar 這個類
// https://github.com/liu844869663/mybatis-spring/blob/master/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
上面只是簡單的提一下,可以看到會通過 TrackedConditionEvaluator 計算器進行計算,判斷是否滿足條件
這裡提一下,對於 @Bean
標註的方法,會得到 CGLIB 的提升,也就是返回的是一個代理物件,設定一個攔截器專門對 @Bean
方法進行攔截處理,通過依賴查詢的方式從 IoC 容器中獲取 Bean 物件,如果是單例 Bean,那麼每次都是返回同一個物件,所以當主動呼叫這個方法時獲取到的都是同一個物件。
SpringBootCondition
org.springframework.boot.autoconfigure.condition.SpringBootCondition
抽象類,實現了 Condition 介面,Spring Boot 擴充套件 Condition 的抽象基類,主要用於列印相應的日誌,並記錄每次的匹配結果,如下:
/**
* Base of all {@link Condition} implementations used with Spring Boot. Provides sensible
* logging to help the user diagnose what classes are loaded.
*
* @author Phillip Webb
* @author Greg Turnquist
* @since 1.0.0
*/
public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(getClass());
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// <1> 從註解元資訊中獲取所標註的`類名`(或者`類名#方法名`)
String classOrMethodName = getClassOrMethodName(metadata);
try {
// <2> 獲取匹配結果(包含匹配訊息),抽象方法,交由子類實現
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// <3> 列印匹配日誌
logOutcome(classOrMethodName, outcome);
// <4> 向 ConditionEvaluationReport 中記錄本次的匹配結果
recordEvaluation(context, classOrMethodName, outcome);
// <5> 返回匹配結果
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
// 丟擲異常
} catch (RuntimeException ex) {
// 丟擲異常
}
}
}
實現的 Condition 介面方法處理過程如下:
-
從註解元資訊中獲取所標註的
類名
(或者類名#方法名
)private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) { if (metadata instanceof ClassMetadata) { ClassMetadata classMetadata = (ClassMetadata) metadata; return classMetadata.getClassName(); } MethodMetadata methodMetadata = (MethodMetadata) metadata; return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName(); }
-
呼叫
getMatchOutcome(..)
方法,獲取匹配結果(包含匹配訊息),抽象方法,交由子類實現 -
列印匹配日誌
protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) { if (this.logger.isTraceEnabled()) { this.logger.trace(getLogMessage(classOrMethodName, outcome)); } }
-
向 ConditionEvaluationReport 中記錄本次的匹配結果
private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) { if (context.getBeanFactory() != null) { ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this, outcome); } }
-
返回匹配結果
SpringBootCondition 的實現類
OnClassCondition
org.springframework.boot.autoconfigure.condition.OnClassCondition
,繼承 SpringBootCondition 抽象類,如下:
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
/**
* 該方法來自 {@link SpringBootCondition} 判斷某個 Bean 是否符合注入條件(`@ConditionalOnClass` 和 `ConditionalOnMissingClass`)
*/
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
// <1> 獲取這個類上面的 `@ConditionalOnClass` 註解的值
// 也就是哪些 Class 物件必須存在
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
// <1.1> 找到這些 Class 物件中哪些是不存在的
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
// <1.2> 如果存在不存在的,那麼不符合條件,返回不匹配
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
}
// <1.3> 新增 `@ConditionalOnClass` 滿足條件的匹配資訊
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes")
.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
// <2> 獲取這個類上面的 `@ConditionalOnMissingClass` 註解的值
// 也就是這些 Class 物件必須都不存在
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
// <2.1> 找到這些 Class 物件中哪些是存在的
List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
// <2.2> 如果有一個存在,那麼不符合條件,返回不匹配
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
}
// <2.3> 新增 `@ConditionalOnMissingClass` 滿足條件的匹配資訊
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes")
.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
}
// <3> 返回符合條件的結果
return ConditionOutcome.match(matchMessage);
}
}
判斷是否匹配的過程如下:
-
獲取這個類上面的
@ConditionalOnClass
註解的值,也就是哪些 Class 物件必須存在-
找到這些 Class 物件中哪些是不存在的
protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter, ClassLoader classLoader) { // 如果為空,則返回空結果 if (CollectionUtils.isEmpty(classNames)) { return Collections.emptyList(); } List<String> matches = new ArrayList<>(classNames.size()); // 使用 `classNameFilter` 對 `classNames` 進行過濾 for (String candidate : classNames) { if (classNameFilter.matches(candidate, classLoader)) { matches.add(candidate); } } // 返回匹配成功的 `className` 們 return matches; }
-
如果存在不存在的,那麼不符合條件,返回不匹配
-
新增
@ConditionalOnClass
滿足條件的匹配資訊
-
-
獲取這個類上面的
@ConditionalOnMissingClass
註解的值,也就是這些 Class 物件必須都不存在- 找到這些 Class 物件中哪些是存在的,和上面的
1.1
差不多,只不過這裡傳的是 ClassNameFilter.PRESENT 過濾器 - 如果有一個存在,那麼不符合條件,返回不匹配
- 新增
@ConditionalOnMissingClass
滿足條件的匹配資訊
- 找到這些 Class 物件中哪些是存在的,和上面的
-
返回符合條件的結果
上面使用到的 ClassNameFilter 如下:
protected enum ClassNameFilter {
/** 指定類存在 */
PRESENT {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return isPresent(className, classLoader);
}
},
/** 指定類不存在 */
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
abstract boolean matches(String className, ClassLoader classLoader);
static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
// 載入指定類,載入成功表示存在這個類
resolve(className, classLoader);
return true;
}
catch (Throwable ex) {
// 載入失敗表示不存在這個類
return false;
}
}
}
邏輯很簡單,就是判斷 Class 物件是否存在或者不存在
其它實現類
關於 SpringBootCondition 其他的實現類邏輯都差不多,感興趣的可以去看看
回顧自動配置
在上一篇《剖析 @SpringBootApplication 註解》 文章分析通過 @EnableAutoConfiguration
註解驅動整個自動配置模組的過程中,會通過指定的 AutoConfigurationImportFilter 對所有的自動配置類進行過濾,滿足條件才進行自動配置
可以回顧一下上一篇文章的 2. getAutoConfigurationEntry 方法 小節和 3. filter 方法 小節
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// <1> 如果通過 `spring.boot.enableautoconfiguration` 配置關閉了自動配置功能
if (!isEnabled(annotationMetadata)) {
// 則返回一個“空”的物件
return EMPTY_ENTRY;
}
// <2> 獲取 `@EnableAutoConfiguration` 註解的配置資訊
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// <3> 從所有的 `META-INF/spring.factories` 檔案中找到 `@EnableAutoConfiguration` 註解對應的類(需要自動配置的類)
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// <4> 對所有的自動配置類進行去重
configurations = removeDuplicates(configurations);
// <5> 獲取需要排除的自動配置類
// 可通過 `@EnableAutoConfiguration` 註解的 `exclude` 和 `excludeName` 配置
// 也可以通過 `spring.autoconfigure.exclude` 配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// <6> 處理 `exclusions` 中特殊的類名稱,保證能夠排除它
checkExcludedClasses(configurations, exclusions);
// <7> 從 `configurations` 中將 `exclusions` 需要排除的自動配置類移除
configurations.removeAll(exclusions);
/**
* <8> 從 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportFilter} 對 `configurations` 進行過濾處理
* 例如 Spring Boot 中配置了 {@link org.springframework.boot.autoconfigure.condition.OnClassCondition}
* 在這裡提前過濾掉一些不滿足條件的自動配置類,在 Spring 注入 Bean 的時候也會判斷哦~
*/
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
// <10> 將所有的自動配置類封裝成一個 AutoConfigurationEntry 物件,並返回
return new AutoConfigurationEntry(configurations, exclusions);
}
我們看到第 8
步,呼叫 filter(..)
方法, 目的就是過濾掉一些不符合 Condition 條件的自動配置類
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
// <1> 將自動配置類儲存至 `candidates` 陣列中
String[] candidates = StringUtils.toStringArray(configurations);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
/*
* <2> 從 `META-INF/spring.factories` 找到所有的 AutoConfigurationImportFilter 對 `candidates` 進行過濾處理
* 有 OnClassCondition、OnBeanCondition、OnWebApplicationCondition
*/
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
// <2.1> Aware 回撥
invokeAwareMethods(filter);
// <2.2> 對 `candidates` 進行匹配處理,獲取所有的匹配結果
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
// <2.3> 遍歷匹配結果,將不匹配的自動配置類至空
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
candidates[i] = null;
skipped = true;
}
}
}
// <3> 如果沒有不匹配的結果則全部返回
if (!skipped) {
return configurations;
}
// <4> 獲取到所有匹配的自動配置類,並返回
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
return new ArrayList<>(result);
}
可以看到第 2
步,會從 META-INF/spring.factories
中找到對應的 AutoConfigurationImportFilter 實現類對所有的自動配置類進行過濾
# 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
這裡,我們一定要注意到入參中的 AutoConfigurationMetadata
物件,它裡面儲存了 META-INF/spring-autoconfigure-metadata.properties
檔案中 Spring Boot 的自動配置類的註解元資訊(Sprng Boot 編譯時生成的),如何來的請回顧上一篇文章
AutoConfigurationImportFilter
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
介面,用於過濾掉無需自動引入的自動配置類
/**
* Filter that can be registered in {@code spring.factories} to limit the
* auto-configuration classes considered. This interface is designed to allow fast removal
* of auto-configuration classes before their bytecode is even read.
*
* @author Phillip Webb
* @since 1.5.0
*/
@FunctionalInterface
public interface AutoConfigurationImportFilter {
boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}
可以看到它的註釋,因為自動配置類會很多,如果無需使用,而建立對應的 Bean(位元組碼)到 JVM 記憶體中,將是一種浪費
可以看到它的最終實現類,都是構建在 SpringBootCondition 之上。? 不過這也很正常,因為 Condition 本身提供的一個功能,就是作為配置類(Configuration)是否能夠符合條件被引入。
FilteringSpringBootCondition
org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition
,繼承 SpringBootCondition 抽象類,實現 AutoConfigurationImportFilter 介面,作為具有 AutoConfigurationImportFilter 功能的 SpringBootCondition 的抽象基類。
abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
/**
* 底層 IoC 容器
*/
private BeanFactory beanFactory;
/**
* 類載入器
*/
private ClassLoader beanClassLoader;
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
// <1> 從 Spring 應用上下文中獲取 ConditionEvaluationReport 物件
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
// <2> 獲取所有自動配置類的匹配結果,空方法,交由子類實現
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
// <3> 將自動配置類的匹配結果儲存至一個 `boolean[]` 陣列中,並將匹配結果一一儲存至 ConditionEvaluationReport 中
boolean[] match = new boolean[outcomes.length];
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]);
}
}
}
// <4> 返回所有自動配置類是否滿足條件的結果陣列
return match;
}
}
可以看到,這實現方法和 SpringBootCondition 的 match(..)
方法很想,他們的入參不同,不要搞混了,這裡的處理過程如下:
- 從 Spring 應用上下文中獲取 ConditionEvaluationReport 物件
- 呼叫
getOutcomes(..)
抽象方法,獲取所有自動配置類的匹配結果,空方法,交由子類實現 - 將自動配置類的匹配結果儲存至一個
boolean[]
陣列中,並將匹配結果一一儲存至 ConditionEvaluationReport 中- 注意這裡匹配結果為空也表示匹配成功
- 返回所有自動配置類是否滿足條件的結果陣列
FilteringSpringBootCondition 的實現類
OnClassCondition
org.springframework.boot.autoconfigure.condition.OnClassCondition
,繼承 FilteringSpringBootCondition 抽象類,如下:
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
/**
* 該方法來自 {@link AutoConfigurationImportFilter} 判斷這些自動配置類是否符合條件(`@ConditionalOnClass`)
*/
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// Split the work and perform half in a background thread if more than one
// processor is available. Using a single additional thread seems to offer the
// best performance. More threads make things worse.
// 考慮到自動配置類上面的 `@Conditional` 相關注解比較多,所以採用多執行緒以提升效率。經過測試使用,使用兩個執行緒的效率是最高的,
// 所以會將 `autoConfigurationClasses` 一分為二進行處理
// <1> 如果 JVM 可用的處理器不止一個,那麼這裡用兩個執行緒去處理
if (Runtime.getRuntime().availableProcessors() > 1) {
// <1.1> 對 `autoConfigurationClasses` 所有的自動配置類進行處理
// 這裡是對 `@ConditionalOnClass` 註解進行處理,必須存在指定 Class 類物件
return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
}
// <2> 否則,就是單核處理,當前執行緒去處理
else {
// <2.1> 建立一個匹配處理器 `outcomesResolver`
OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
// <2.2> 返回 `outcomesResolver` 的執行結果
// 這裡是對 `@ConditionalOnClass` 註解進行處理,必須存在指定 Class 類物件
return outcomesResolver.resolveOutcomes();
}
}
}
過濾所有自動配置類的的過程如下:
考慮到自動配置類上面的 @Conditional
相關注解比較多,所以採用多執行緒以提升效率。經過測試使用,使用兩個執行緒的效率是最高的,所以會嘗試將 autoConfigurationClasses
一分為二進行處理
- 如果 JVM 可用的處理器不止一個,那麼這裡用兩個執行緒去處理
- 對
autoConfigurationClasses
所有的自動配置類進行處理,這裡是對@ConditionalOnClass
註解進行處理,必須存在指定 Class 類物件
- 對
- 否則,就是單核處理,當前執行緒去處理
- 建立一個匹配處理器
outcomesResolver
- 返回
outcomesResolver
的執行結果,這裡是對@ConditionalOnClass
註解進行處理,必須存在指定 Class 類物件
- 建立一個匹配處理器
resolveOutcomesThreaded 方法
private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// <1> 將自動配置類的個數一分為二
int split = autoConfigurationClasses.length / 2;
// <2> 建立一個 StandardOutcomesResolver 匹配處理器,另起一個執行緒去處理前一半的自動配置類
OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
autoConfigurationMetadata);
// <3> 建立一個 StandardOutcomesResolver 匹配處理器,當前執行緒去處理後一半的自動配置類
OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
// <4> 獲取兩個匹配器處理器的處理結果,將他們合併,然後返回
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes;
}
邏輯很簡單,就是同時兩個執行緒分半處理,還是通過 StandardOutcomesResolver 匹配處理器來處理
StandardOutcomesResolver 處理器
private final class StandardOutcomesResolver implements OutcomesResolver {
/**
* 需要處理的自動配置類
*/
private final String[] autoConfigurationClasses;
/**
* 區間開始位置
*/
private final int start;
/**
* 區間結束位置
*/
private final int end;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration} 註解的元資訊
*/
private final AutoConfigurationMetadata autoConfigurationMetadata;
/**
* 類載入器
*/
private final ClassLoader beanClassLoader;
@Override
public ConditionOutcome[] resolveOutcomes() {
// 獲取自動配置類的匹配結果
return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}
}
getOutcomes 方法
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
/**
* 遍歷執行區間內的自動配置類
*/
for (int i = start; i < end; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
/**
* 獲取這個自動配置類的 `@ConditionalOnClass` 註解的值
* 這裡不需要解析,而是從一個 `Properties` 中直接獲取
* 參考 {@link AutoConfigurationImportSelector} 中我的註釋
* 參考 {@link AutoConfigureAnnotationProcessor}
*/
String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
// 如果值不為空,那麼這裡先進行匹配處理,判斷這個自動配置類是否需要注入,也就是是否存在 指定的 Class 物件(`candidates`)
// 否則,不進行任何處理,也就是不過濾掉
if (candidates != null) {
// 判斷指定的 Class 類物件是否都存在,都存在返回 `null`,有一個不存在返回不匹配
outcomes[i - start] = getOutcome(candidates);
}
}
}
return outcomes;
}
private ConditionOutcome getOutcome(String candidates) {
try {
// 這個配置類的 `@ConditionalOnClass` 註解只指定了一個,則直接處理
if (!candidates.contains(",")) {
// 判斷這個 Class 類物件是否存在,存在返回 `null`,不存在返回不匹配
return getOutcome(candidates, this.beanClassLoader);
}
// 這個配置類的 `@ConditionalOnClass` 註解指定了多個,則遍歷處理,必須都存在
for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
// 判斷這個 Class 類物件是否存在,存在返回 `null`,不存在返回不匹配
ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
// 如果不為空,表示不匹配,直接返回
if (outcome != null) {
return outcome;
}
}
}
catch (Exception ex) { }
return null;
}
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
// 如果這個 Class 物件不存在,則返回不匹配
if (ClassNameFilter.MISSING.matches(className, classLoader)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class").items(Style.QUOTE, className));
}
return null;
}
邏輯比較簡單,先從 AutoConfigurationMetadata 這個物件中找到這個自動配置類 @ConditionalOnClass
註解的值,如下:
@Override
public String get(String className, String key) {
return get(className, key, null);
}
@Override
public String get(String className, String key, String defaultValue) {
// 獲取 `類名.註解簡稱` 對應的值,也就是這個類上面該註解的值
String value = this.properties.getProperty(className + "." + key);
// 如果存在該註解的值,則返回,沒有的話返回指定的預設值
return (value != null) ? value : defaultValue;
}
如果沒有該註解,匹配結果為空,也表示匹配成功,存在該註解,則對指定的 Class 物件進行判斷,如果有一個 Class 物件不存在,匹配結果則是不匹配,所以這個 AutoConfigurationMetadata 對於過濾自動配置類很關鍵
其它實現類
另外兩個 OnBeanCondition 和 OnWebApplicationCondition 實現類的原理差不多,感興趣的可以去看看
AutoConfigurationMetadata
org.springframework.boot.autoconfigure.AutoConfigurationMetadata
介面,僅有一個 PropertiesAutoConfigurationMetadata 實現類
這個物件很關鍵,在文中提到不少次數,是解析 META-INF/spring-autoconfigure-metadata.properties
檔案生成的物件,裡面儲存了 Spring Boot 中自動配置類的註解後設資料
至於如何解析這個檔案,如何生成該物件,這裡不在講述,可以回顧上一篇《剖析 @SpringBootApplication 註解》 文章的 AutoConfigureAnnotationProcessor 小節
下面列舉檔案中的部分內容
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration=
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureOrder=-2147483648
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration=
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations
看到這個檔案是不是明白了,判斷是否要引入 DispatcherServletAutoConfiguration
這個自動配置類,從這個 properties
檔案中找到它的 @ConditionalOnClass
註解的值,然後判斷指定的 Class 物件們是否都存在,不存在則表示不需要引入這個自動配置類
當然,還有 @OnBeanCondition
和 @OnWebApplicationCondition
兩個註解的判斷
總結
本文分析了 Spring 的 Condition 介面,配合 @Conditional
註解的使用,可以判斷某個 Class 物件是否有必要生成一個 Bean 被 Spring IoC 容器管理。
由於在 Spring 中 Condition 介面的實現類就一個,Spring Boot 則對 Condition 介面進行擴充套件,豐富了更多的使用場景,其內部主要用於自動配置類。同時,在 Spring Boot 中提供了很多 Condition 相關的註解,配合 @Conditional
註解一起使用,就能將滿足條件的自動配置類引入進來。例如 @OnClassCondition
註解標註的配置類必須存在所有的指定的 Class 物件們,才將其生成一個 Bean 被 Spring IoC 容器管理。
在 @EnableAutoConfiguration
註解驅動自動配置模組的過程中,會通過 AutoConfigurationImportFilter
過濾掉一些不滿足條件的自動配置類,原理和 Condition 差不多,主要通過上面這個 AutoConfigurationMetadata
類,再結合不同的 AutoConfigurationImportFilter
實現類實現的。