參考 知識星球 中 芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄
該系列文章是筆者在學習 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
實現類實現的。