注:該源碼分析對應SpringBoot版本為2.1.0.RELEASE
1 前言
本篇接
如何分析SpringBoot源碼模塊及結構?--SpringBoot源碼(二)
上一篇分析了SpringBoot源碼結構及各個模塊pom之間的關系后,那么此篇開始就開始解開SpringBoot新特性之一--自動配置的神秘面紗了。因為SpringBoot自動配置原理是基於其大量的條件注解ConditionalOnXXX,因此,本節我們先來擼下Spring的條件注解的相關源碼。
2 SpringBoot的派生條件注解
我們都知道,SpringBoot自動配置是需要滿足相應的條件才會自動配置,因此SpringBoot的自動配置大量應用了條件注解ConditionalOnXXX。如下圖:

那么上圖的條件注解如何使用呢?
舉個栗子,我們來看下如何使用
@ConditionalOnClass和@ConditionalOnProperty這兩個注解,先看下圖代碼:
HelloWorldEnableAutoConfiguration這個自動配置類應用了@ConditionalOnClass和ConditionalOnProperty兩個條件注解,那么只有在滿足:classpath中存在HelloWorldComponent.class和配置了hello.world.name和hello.world.age屬性這兩個條件的情況下才會創建HelloWorldComponent這個bean。
其實SpringBoot的@ConditionalOnXXX等條件注解都是派生注解,那么什么是派生注解呢?
就拿上面的栗子來說,以@ConditionalOnClass(HelloWorldComponent.class)為例,我們打開ConditionalOnClass注解源碼,如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
可以看到@ConditionalOnClass注解上面又標注了@Conditional(OnClassCondition.class)注解,因此@ConditionalOnClass是@Conditional的派生注解,@Conditional(OnClassCondition.class)和@ConditionalOnClass注解是等價的,即這兩個注解標注在某個配置類上的效果是等價的。
而SpringBoot的自動配置原理正是建立在這些大量的派生條件注解@ConditionalOnXXX之上,而這些條件注解的原理跟Spring的Condition接口有關。因此我們先來研究下Condition接口的相關源碼。
3 Condition接口
3.1 Condition接口源碼分析
分析Condition接口源碼前先看下如何自定義ConditionalOnXXX注解,舉個栗子,比如自定義一個@ConditionalOnLinux注解,該注解只有在其屬性environment是"linux"才會創建相關的bean。定義了以下代碼:
/**
* 實現spring 的Condition接口,並且重寫matches()方法,如果@ConditionalOnLinux的注解屬性environment是linux就返回true
*
*/
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 獲得注解@ConditionalOnLinux的所有屬性
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(
ConditionalOnLinux.class.getName()));
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
// 獲得注解@ConditionalOnLinux的environment屬性
String environment = annotationAttributes.getString("environment");
// 若environment等於linux,則返回true
if ("linux".equals(environment)) {
return true;
}
}
return false;
}
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(LinuxCondition.class)
public @interface ConditionalOnLinux {
// 標注是哪個環境
String environment() default "";
}
@Configuration
public class ConditionConfig {
// 只有`@ConditionalOnLinux`的注解屬性`environment`是"linux"時才會創建bean
@Bean
@ConditionalOnLinux(environment = "linux")
public Environment linuxEnvironment() {
return new LinuxEnvironment();
}
}
上面的代碼我們捋一下:
LinuxCondition實現了Condition接口並實現了matches方法,而matches方法則判斷@ConditionalOnLinux的注解屬性environment是否"linux",是則返回true,否則false。- 然后我們再定義了一個注解
@ConditionalOnLinux,這個注解是@Conditional的派生注解,與@Conditional(LinuxCondition.class)等價,注意@ConditionalOnLinux注解定義了一個屬性environment。而我們最終可以利用LinuxCondition的matches方法中的參數AnnotatedTypeMetadata來獲取@ConditionalOnLinux的注解屬性environment的值,從而用來判斷值是否為linux"。 - 最后我們又定義了一個配置類
ConditionConfig,在linuxEnvironment方法上標注了@ConditionalOnLinux(environment = "linux")。因此,這里只有LinuxCondition的matches方法返回true才會創建bean。
學會了如何自定義@ConditionalOnXXX注解后,我們現在再來看下Condition接口的源碼:
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Condition接口主要有一個matches方法,該方法決定了是否要注冊相應的bean對象。其中matches方法中有兩個參數,參數類型分別是ConditionContext和AnnotatedTypeMetadata,這兩個參數非常重要。它們分別用來獲取一些環境信息和注解元數據從而用在matches方法中判斷是否符合條件。
ConditionContext,顧名思義,主要是跟Condition的上下文有關,主要用來獲取Registry,BeanFactory,Environment,ResourceLoader和ClassLoader等。那么獲取這些用來干什么呢?舉個栗子,比如OnResourceCondition需要靠ConditionContext來獲取ResourceLoader來加載指定資源,OnClassCondition需要靠ConditionContext來獲取ClassLoader來加載指定類等,下面看下其源碼:
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getClassLoader();
}
AnnotatedTypeMetadata,這個跟注解元數據有關,利用AnnotatedTypeMetadata可以拿到某個注解的一些元數據,而這些元數據就包含了某個注解里面的屬性,比如前面的栗子,利用AnnotatedTypeMetadata可以拿到@ConditionalOnLinux的注解屬性environment的值。下面看下其源碼:
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationName);
Map<String, Object> getAnnotationAttributes(String annotationName);
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
回到剛才的栗子,我們知道@ConditionalOnLinux注解真正起作用的是Condition接口的具體實現類LinuxCondition的matches方法,那么這個matches方法是在何時被調用的呢?
通過idea調試看調用的棧幀,如下圖:

發現是在ConditionEvaluator的shouldSkip方法中調用了LinuxCondition的matches方法,自然我們再去看看ConditionEvaluator的shouldSkip的方法執行了什么邏輯。
// 這個方法主要是如果是解析階段則跳過,如果是注冊階段則不跳過
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 若沒有被@Conditional或其派生注解所標注,則不會跳過
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 沒有指定phase,注意phase可以分為PARSE_CONFIGURATION或REGISTER_BEAN類型
if (phase == null) {
// 若標有@Component,@Import,@Bean或@Configuration等注解的話,則說明是PARSE_CONFIGURATION類型
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
// 否則是REGISTER_BEAN類型
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
// TODO 獲得所有標有@Conditional注解或其派生注解里面的Condition接口實現類並實例化成對象。
// 比如@Conditional(OnBeanCondition.class)則獲得OnBeanCondition.class,OnBeanCondition.class往往實現了Condition接口
for (String[] conditionClasses : getConditionClasses(metadata)) {
// 將類實例化成對象
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 排序,即按照Condition的優先級進行排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
// 從condition中獲得對bean是解析還是注冊
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// 若requiredPhase為null或獲取的階段類型正是當前階段類型且不符合condition的matches條件,則跳過
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
shouldSkip這個方法執行的邏輯主要是如果是解析階段則跳過,如果是注冊階段則不跳過;如果是在注冊階段即REGISTER_BEAN階段的話,此時會得到所有的Condition接口的具體實現類並實例化這些實現類,然后再執行下面關鍵的代碼進行判斷是否需要跳過。
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
上面代碼最重要的邏輯是調用了Condition接口的具體實現類的matches方法,若matches返回false,則跳過,不進行注冊bean的操作;若matches返回true,則不跳過,進行注冊bean的操作;
好了,Condition的源碼分析就到此為止,再往上翻調用方法的話應該就是Spring加載bean定義的相關源碼了,不屬於這里的分析范圍。
3.2 Spring的內置Condition接口實現類
前面我們學會了如何自定義條件注解及Condition的源碼分析,那么我們不禁好奇,Spring究竟內置了哪些Condition接口的實現類呢?
那么看下Spring的Condition接口的具體實現類的類圖:

發現Spring內置的Condition接口的具體實現類雖然有多個,但只有ProfileCondition不是測試相關的,因此可以說真正的內置的Condition接口的具體實現類只有ProfileCondition一個,非常非常少,這跟SpringBoot的大量派生條件注解形成了鮮明的對比。ProfileCondition大家都知道,是跟環境有關,比如我們平時一般有dev,test和prod環境,而ProfileCondition就是判斷我們項目配置了哪個環境的。下面是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;
}
}
4 SpringBootCondition源碼解析
前面看到Spring對Condition的內置注解可以說只有ProfileCondition一個,但是我們都知道,SpringBoot則內置了大量的條件注解ConditionalOnXXX。在分析前,我們先來看一下SpringBootCondition的整體類圖來個整體的理解,如下圖:

可以看到SpringBootCondition作為SpringBoot條件注解的基類,處於整個類圖的中心,它實現了Condition接口,然后又有很多具體的子類OnXXXCondition,這些OnXXXCondition其實就是@ConditionalOnXXX的條件類。
我們先來看下SpringBootCondition這個父類是主要做了哪些事情,抽象了哪些共有的邏輯?
SpringBootConditon實現了Condition接口,作為SpringBoot眾多條件注解OnXXXCondtion的父類,它的作用主要就是打印一些條件注解評估報告的日志,比如打印哪些配置類是符合條件注解的,哪些是不符合的。打印的日志形式如下圖:


因為SpringBootConditon實現了Condition接口,也實現了matches方法,因此該方法同樣也是被ConditionEvaluator的shouldSkip方法中調用,因此我們就以SpringBootConditon的matches方法為入口去進行分析。直接上代碼:
// SpringBootCondition.java
public final boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 得到metadata的類名或方法名
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 判斷每個配置類的每個條件注解@ConditionalOnXXX是否滿足條件,然后記錄到ConditionOutcome結果中
// 注意getMatchOutcome是一個抽象模板方法,交給OnXXXCondition子類去實現
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 打印condition評估的日志,哪些條件注解@ConditionalOnXXX是滿足條件的,哪些是不滿足條件的,這些日志都打印出來
logOutcome(classOrMethodName, outcome);
// 除了打印日志外,這些是否匹配的信息還要記錄到ConditionEvaluationReport中
recordEvaluation(context, classOrMethodName, outcome);
// 最后返回@ConditionalOnXXX是否滿足條件
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException(
"Could not evaluate condition on " + classOrMethodName + " due to "
+ ex.getMessage() + " not "
+ "found. Make sure your own configuration does not rely on "
+ "that class. This can also happen if you are "
+ "@ComponentScanning a springframework package (e.g. if you "
+ "put a @ComponentScan in the default package by mistake)",
ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException(
"Error processing condition on " + getName(metadata), ex);
}
}
上面代碼的注釋已經非常詳細,我們知道了SpringBootCondition抽象了所有其具體實現類OnXXXCondition的共有邏輯--condition評估信息打印,最重要的是封裝了一個模板方法getMatchOutcome(context, metadata),留給各個OnXXXCondition具體子類去覆蓋實現屬於自己的判斷邏輯,然后再返回相應的匹配結果給SpringBootCondition用於日志打印。
因此我們知道了SpringBootCondition其實就是用來打印condition評估信息的,對於其他枝節方法我們不必追究過深,免得丟了主線。我們現在的重點是放在交給OnXXXCondition子類實現的模板方法上getMatchOutcome(context, metadata);,因為這個方法將會由很多OnXXXCondition覆蓋重寫判斷邏輯,這里是我們接下來分析的重點。
因為SpringBootCondition有眾多具體實現類,下面只挑OnResourceCondition,OnBeanCondition和OnWebApplicationCondition進行講解,而AutoConfigurationImportFilter跟自動配置有關,則留到自動配置源碼解析的時候再進行分析。
4.1 OnResourceCondition源碼分析
現在先來看下一個邏輯及其簡單的注解條件類OnResourceCondition,OnResourceCondition繼承了SpringBootCondition父類,覆蓋了其getMatchOutcome方法,用於@ConditionalOnResource注解指定的資源存在與否。OnResourceCondition的判斷邏輯非常簡單,主要拿到@ConditionalOnResource注解指定的資源路徑后,然后用ResourceLoader根據指定路徑去加載看資源存不存在。下面直接看代碼:
先來看下@ConditionalOnResource的代碼,
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnResourceCondition.class)
public @interface ConditionalOnResource {
/**
* The resources that must be present.
* @return the resource paths that must be present.
*/
String[] resources() default {};
}
再來看OnResourceCondition的代碼:
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnResourceCondition extends SpringBootCondition {
private final ResourceLoader defaultResourceLoader = new DefaultResourceLoader();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 獲得@ConditionalOnResource注解的屬性元數據
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true);
// 獲得資源加載器,若ConditionContext中有ResourceLoader則用ConditionContext中的,沒有則用默認的
ResourceLoader loader = (context.getResourceLoader() != null)
? context.getResourceLoader() : this.defaultResourceLoader;
List<String> locations = new ArrayList<>();
// 將@ConditionalOnResource中定義的resources屬性值取出來裝進locations集合
collectValues(locations, attributes.get("resources"));
Assert.isTrue(!locations.isEmpty(),
"@ConditionalOnResource annotations must specify at "
+ "least one resource location");
// missing集合是裝不存在指定資源的資源路徑的
List<String> missing = new ArrayList<>();
// 遍歷所有的資源路徑,若指定的路徑的資源不存在則將其資源路徑存進missing集合中
for (String location : locations) {
// 這里針對有些資源路徑是Placeholders的情況,即處理${}
String resource = context.getEnvironment().resolvePlaceholders(location);
if (!loader.getResource(resource).exists()) {
missing.add(location);
}
}
// 如果存在某個資源不存在,那么則報錯
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnResource.class)
.didNotFind("resource", "resources").items(Style.QUOTE, missing));
}
// 所有資源都存在,那么則返回能找到就提的資源
return ConditionOutcome
.match(ConditionMessage.forCondition(ConditionalOnResource.class)
.found("location", "locations").items(locations));
}
// 將@ConditionalOnResource中定義的resources屬性值取出來裝進locations集合
private void collectValues(List<String> names, List<Object> values) {
for (Object value : values) {
for (Object item : (Object[]) value) {
names.add((String) item);
}
}
}
}
可以看到OnResourceCondition的getMatchOutcome方法非常簡單,這里不再詳述。
4.2 OnBeanCondition源碼分析
OnBeanCondition同樣繼承了FilteringSpringBootCondition父類,覆蓋了父類FilteringSpringBootCondition的getOutcomes方法。而FilteringSpringBootCondition又是SpringBootCondition的子類,FilteringSpringBootCondition跟自動配置類過濾有關,這里先不分析。值得注意的是OnBeanCondition同樣重寫了SpringBootCondition的getMatchOutcome方法,用來判斷Spring容器中是否存在指定條件的bean。同時是OnBeanCondition是@ConditionalOnBean,@ConditionalOnSingleCandidate和ConditionalOnMissingBean的條件類。
同樣,先來看OnBeanCondition復寫父類SpringBootCondition的getMatchOutcome方法的代碼:
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
// (1),配置類(metadata)標注@ConditionalOnBean注解的情況
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
// 將@ConditionalOnBean注解屬性封裝進BeanSearchSpec對象中
// 注意BeanSearchSpec是一個靜態內部類,用來存儲@ConditionalOnBean和@ConditionalOnMissingBean注解的屬性值
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnBean.class);
// 調用getMatchingBeans得到符合條件的bean
MatchResult matchResult = getMatchingBeans(context, spec);
// 如果不匹配
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnBean.class, spec).because(reason));
}
// 如果匹配
matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
.found("bean", "beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
}
// (2),配置類(metadata)標注@ConditionalOnSingleCandidate注解的情況
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,
ConditionalOnSingleCandidate.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnSingleCandidate.class, spec)
.didNotFind("any beans").atAll());
}
else if (!hasSingleAutowireCandidate(context.getBeanFactory(),
matchResult.getNamesOfAllMatches(),
spec.getStrategy() == SearchStrategy.ALL)) {
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnSingleCandidate.class, spec)
.didNotFind("a primary bean from beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
}
matchMessage = matchMessage
.andCondition(ConditionalOnSingleCandidate.class, spec)
.found("a primary bean from beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
}
// (3),配置類(metadata)標注@ConditionalOnMissingBean注解的情況
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (matchResult.isAnyMatched()) {
String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnMissingBean.class, spec)
.because(reason));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
.didNotFind("any beans").atAll();
}
// 最終返回matchMessage
return ConditionOutcome.match(matchMessage);
}
我們可以看到OnBeanCondition類覆蓋的getMatchOutcome方法分別處理了標注@ConditionalOnBean,@ConditionalOnSingleCandidate和@ConditionalOnMissingBean注解的情況,分別對應上面代碼注釋的(1),(2)和(3)處。
現在我們只看針對@ConditionalOnBean注解的處理邏輯,從上面代碼中可以看到若配置類(metadata)標注@ConditionalOnBean注解的話,主要做了以下事情:
- 將該注解屬性提取出來封裝進
BeanSearchSpec對象中; - 然后調用
getMatchingBeans(context, spec)方法來獲取是否有匹配的bean; - 最后返回
bean的匹配情況;
可以看到最重要的邏輯是第2步,那么我們再來看下getMatchingBeans方法,直接上代碼:
protected final MatchResult getMatchingBeans(ConditionContext context,
BeanSearchSpec beans) {
// 獲得Spring容器的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 判斷bean的搜索策略是否是SearchStrategy.ANCESTORS策略
if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
"Unable to use SearchStrategy.PARENTS");
beanFactory = (ConfigurableListableBeanFactory) parent;
}
// MatchResult用來存儲bean的匹配結果
MatchResult matchResult = new MatchResult();
// 如果bean的搜索策略不是SearchStrategy.CURRENT的話,則置considerHierarchy為true
boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
// 獲取TypeExtractor,TypeExtractor是用來判斷bean的類型的
TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader());
// 獲取是否有被忽略bean類型,若有的話將該bean類型的名稱裝進beansIgnoredByType集合
// 這里主要是針對@ConditionalOnMissingBean的ignored屬性
List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(
beans.getIgnoredTypes(), typeExtractor, beanFactory, context,
considerHierarchy);
// 遍歷bean的所有類型
for (String type : beans.getTypes()) {
// 調用getBeanNamesForType方法根據bean類型得到所有符合條件的bean類型,並放到typeMatches集合
Collection<String> typeMatches = getBeanNamesForType(beanFactory, type,
typeExtractor, context.getClassLoader(), considerHierarchy);
// 移除掉Ignored的類型
typeMatches.removeAll(beansIgnoredByType);
// 若typeMatches為空,那么則說明正在遍歷的這個type類型不符合匹配條件,此時用matchResult記錄一下這個不符合條件的類型
if (typeMatches.isEmpty()) {
matchResult.recordUnmatchedType(type);
}
// 若typeMatches不為空,那么則說明正在遍歷的這個type類型符合匹配條件,此時用matchResult記錄一下這個符合條件的類型
else {
matchResult.recordMatchedType(type, typeMatches);
}
}
// 這里針對@ConditionalOnBean等注解的annotation屬性的處理
for (String annotation : beans.getAnnotations()) {
List<String> annotationMatches = Arrays
.asList(getBeanNamesForAnnotation(beanFactory, annotation,
context.getClassLoader(), considerHierarchy));
annotationMatches.removeAll(beansIgnoredByType);
if (annotationMatches.isEmpty()) {
matchResult.recordUnmatchedAnnotation(annotation);
}
else {
matchResult.recordMatchedAnnotation(annotation, annotationMatches);
}
}
// 這里針對@ConditionalOnBean等注解的name屬性的處理
for (String beanName : beans.getNames()) {
// beansIgnoredByType集合不包含beanName且beanFactory包含這個bean,則匹配
if (!beansIgnoredByType.contains(beanName)
&& containsBean(beanFactory, beanName, considerHierarchy)) {
matchResult.recordMatchedName(beanName);
}
// 否則,不匹配
else {
matchResult.recordUnmatchedName(beanName);
}
}
// 最后返回匹配結果
return matchResult;
}
上面的邏輯主要是從spring容器中搜索有無指定條件的bean,搜索Spring容器搜索bean的話有三種搜索策略,分別是CURRENT,ANCESTORS和ALL,分表表示只從當前的context中搜索bean,只從父context中搜索bean和從整個context中搜索bean;定義了搜索策略后,然后再根據BeanSearchSpec對象封裝的注解屬性分別取指定的容器中查找有無符合條件的bean,然后再進行一些過濾。比如@ConditionalOnMissingBean注解有定義ignored屬性值,那么從容器中搜索到有符合條件的bean時,此時還要移除掉ignored指定的bean。
好了,上面就已經分析了OnBeanCondition這個條件類了,我們堅持主線優先的原則,具體的細節代碼不會深究。
4.3 OnWebApplicationCondition
OnWebApplicationCondition同樣繼承了FilteringSpringBootCondition父類,覆蓋了父類FilteringSpringBootCondition的getOutcomes方法。而FilteringSpringBootCondition又是SpringBootCondition的子類,FilteringSpringBootCondition跟自動配置類過濾有關,這里先不分析。值得注意的是OnWebApplicationCondition同樣重寫了SpringBootCondition的getMatchOutcome方法,用來判斷當前應用是否web應用。同時是OnWebApplicationCondition是@ConditionalOnWebApplication的條件類。
同樣,先來看OnWebApplicationCondition重寫SpringBootCondition的getMatchOutcome方法:
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 配置類是否標注有@ConditionalOnWebApplication注解
boolean required = metadata
.isAnnotated(ConditionalOnWebApplication.class.getName());
// 調用isWebApplication方法返回匹配結果
ConditionOutcome outcome = isWebApplication(context, metadata, required);
// 若有標注@ConditionalOnWebApplication但不符合條件,則返回不匹配
if (required && !outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
// 若沒有標注@ConditionalOnWebApplication但符合條件,則返回不匹配
if (!required && outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
// 這里返回匹配的情況,TODO 不過有個疑問:如果沒有標注@ConditionalOnWebApplication注解,又不符合條件的話,也會執行到這里,返回匹配?
return ConditionOutcome.match(outcome.getConditionMessage());
}
上面代碼的邏輯很簡單,主要是調用isWebApplication方法來判斷當前應用是否是web應用。因此,我們再來看下isWebApplication方法:
private ConditionOutcome isWebApplication(ConditionContext context,
AnnotatedTypeMetadata metadata, boolean required) {
// 調用deduceType方法判斷是哪種類型,其中有SERVLET,REACTIVE和ANY類型,其中ANY表示了SERVLET或REACTIVE類型
switch (deduceType(metadata)) {
// SERVLET類型
case SERVLET:
return isServletWebApplication(context);
// REACTIVE類型
case REACTIVE:
return isReactiveWebApplication(context);
default:
return isAnyWebApplication(context, required);
}
}
在isWebApplication方法中,首先從@ConditionalOnWebApplication注解中獲取其定義了什么類型,然后根據不同的類型進入不同的判斷邏輯。這里我們只看下SERVLET的情況判斷處理,看代碼:
private ConditionOutcome isServletWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
// 若classpath中不存在org.springframework.web.context.support.GenericWebApplicationContext.class,則返回不匹配
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS,
context.getClassLoader())) {
return ConditionOutcome.noMatch(
message.didNotFind("servlet web application classes").atAll());
}
// 若classpath中存在org.springframework.web.context.support.GenericWebApplicationContext.class,那么又分為以下幾種匹配的情況
// session
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match(message.foundExactly("'session' scope"));
}
}
// ConfigurableWebEnvironment
if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
return ConditionOutcome
.match(message.foundExactly("ConfigurableWebEnvironment"));
}
// WebApplicationContext
if (context.getResourceLoader() instanceof WebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
}
// 若以上三種都不匹配的話,則說明不是一個servlet web application
return ConditionOutcome.noMatch(message.because("not a servlet web application"));
}
對於是SERVLET的情況,首先根據classpath中是否存在org.springframework.web.context.support.GenericWebApplicationContext.class,如果不存在該類,則直接返回不匹配;若存在的話那么又分為以下幾種匹配的情況:
- session
- ConfigurableWebEnvironment
- WebApplicationContext
若上面三種情況都不匹配,則說明不是一個servlet web application。
4.4 其他
由於springboot的OnXXXCondition類實現太多,不可能每個條件類都分析一遍,因此上面只分析了OnResourceCondition,OnBeanCondition和onWebApplicationCondition的源碼。我們分析源碼不可能把所有代碼都通讀一遍的,閱讀源碼的話,只要理解了某個模塊的類之間的關系及挑幾個有代表性的類分析下就行,不可能一網打盡。
若有時間的話,推薦看下幾個我們常用的條件類的源碼:OnPropertyCondition,OnClassCondition和OnExpressionCondition等。
5 如何擴展SpringBootCondition
前文我們知道了如何擴展Spring的Condition接口,那么我們該如何擴展SpringBoot的SpringBootCondition類呢?
推薦閱讀springboot之使用SpringBootCondition獲得答案
好了,本篇文章是SpringBoot自動配置源碼分析的前置文章,這里分析了條件注解源碼,那么下篇文章我們就來看看SpringBoot自動配置的源碼了。
下節預告:
SpringBoot新特性:SpringBoot是如何自動配置的?--SpringBoot源碼(四)
原創不易,幫忙點個贊唄!
參考:
3,spring boot 系列之六:深入理解spring boot的自動配置


