Spring?Boot?詳細分析Conditional自動化配置注解
1. Spring Boot Condition功能與作用
@Conditional是基于條件的自動化配置注解, 由Spring 4框架推出的新特性。
在一個服務工程, 通常會存在多個配置環(huán)境, 比如常見的DEV(開發(fā)環(huán)境)、SIT(系統(tǒng)內部集成測試環(huán)境)、UAT(用戶驗收測試環(huán)境)、PRD(生產環(huán)境)等。在Spring3系列版本中通過@Profile實現(xiàn),傳入對應的環(huán)境標識, 系統(tǒng)自動加載不同環(huán)境的配置。spring4版本正式推出Condition功能, 在spring5版本, @Profile做了改進,底層是通過Condition實現(xiàn), 看下Condition接口的UML結構:

可以看到兩個抽象類應用實現(xiàn)了Condition接口, 一個是Spring Context下的ProfileCondition, 另一個就是SpringBootCondition。
SpringBootCondition下面有很多實現(xiàn)類,也是滿足Spring
Boot的各種Condition需要, 圖中只是列出了部分實現(xiàn), 每個實現(xiàn)類下面, 都會有對應的注解來協(xié)助處理。

2. Conditional條件化系列注解介紹
| Conditional的注解 | Conditional的處理類 | Conditional的說明 |
|---|---|---|
| @ConditionalOnBean | OnBeanCondition | Spring容器中是否存在對應的實例??梢酝ㄟ^實例的類型、類名、注解、昵稱去容器中查找(可以配置從當前容器中查找或者父容器中查找或者兩者一起查找) |
| @ConditionalOnClass | OnClassCondition | 類加載器中是否存在對應的類。可以通過Class指定(value屬性)或者Class的全名指定(name屬性)如果是多個類或者多個類名的話,關系是”與”關系,也就是說這些類或者類名都必須同時在類加載器中存在 |
| @ConditionalOnExpression | OnExpressionCondition | 判斷SpEL 表達式是否成立 |
| @ConditionalOnMissingBean | OnBeanCondition | Spring容器中是否缺少對應的實例。可以通過實例的類型、類名、注解、昵稱去容器中查找(可以配置從當前容器中查找或者父容器中查找或者兩者一起查找) |
| @ConditionalOnMissingClass | OnClassCondition | 跟ConditionalOnClass的處理邏輯一樣,只是條件相反,在類加載器中不存在對應的類 |
| @ConditionalOnProperty | OnPropertyCondition | 應用環(huán)境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing屬性。prefix表示屬性名的前綴,name是屬性名,havingValue是具體的屬性值,matchIfMissing是個boolean值,如果屬性不存在,這個matchIfMissing為true的話,會繼續(xù)驗證下去,否則屬性不存在的話直接就相當于匹配不成功 |
| @ConditionalOnResource | OnResourceCondition | 是否存在指定的資源文件。只有一個屬性resources,是個String數組。會從類加載器中去查詢對應的資源文件是否存在 |
| @ConditionalOnSingleCandidate | OnBeanCondition | Spring容器中是否存在且只存在一個對應的實例。只有3個屬性value、type、search。跟ConditionalOnBean中的這3種屬性值意義一樣 |
| @ConditionalOnWebApplication | OnWebApplicationCondition | 應用程序是否是Web程序,沒有提供屬性,只是一個標識。會從判斷Web程序特有的類是否存在,環(huán)境是否是Servlet環(huán)境,容器是否是Web容器等 |
SpringBootCondition下面包含的主要條件化注解說明:
- @ConditionalOnBean: 當Spring容器存在某個Bean則觸發(fā)實現(xiàn)。
- @ConditionalOnMissingBean: 當Spring容器不存在某個Bean則不觸發(fā)。
- @ConditionalOnSingleCandidate: 當Spring容器中只有一個指定Bean,或者多個時是首選 Bean。
- @ConditionalOnClass: 當環(huán)境路徑下有指定的類, 則觸發(fā)實現(xiàn)。
- @ConditionalOnMissingClass: 當環(huán)境路徑下沒有指定類則不觸發(fā)實現(xiàn)。
- @ConditionalOnProperty: 判斷屬性如果存在指定的值則觸發(fā)實現(xiàn)。
- @ConditionalOnResource: 判斷存在指定的資源則觸發(fā)實現(xiàn)。
- @ConditionalOnExpression: 基于 某個SpEL 表達式作判斷實現(xiàn)。
- @ConditionalOnJava:基于JDK的版本作判斷實現(xiàn)。
- @ConditionalOnJndi:基于指定的 JNDI 作判斷實現(xiàn)。
- @ConditionalOnNotWebApplication:判斷當前項目定義如果不是 Web 應用則不觸發(fā)實現(xiàn)。
- @ConditionalOnWebApplication:判斷當前項目定義如果是 Web 應用則觸發(fā)實現(xiàn)。
它們內部都是基于@Conditional實現(xiàn)。
3. Conditional條件化注解的實現(xiàn)原理
上面看到, Spring Boot 有很多內置的多條件化注解, 都是基于@Conditional實現(xiàn),
那么@Conditionnal又是如何實現(xiàn)? 它的作用范圍是什么? 是如何生效的?
Conditional源碼
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* contion條件的具體實現(xiàn)類, 必須實現(xiàn)Condition接口
*/
Class<? extends Condition>[] value();
}@Target標示它的作用范圍是在類或方法上。它是如何被調用生效的? 我們來寫下測試類, 進行調試,
分析調用棧。
自定義Conditional
創(chuàng)建com.mirson.spring.boot.research.condition.CustomerMatchCondition
@Log4j2
public class CustomerMatchCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
log.info("Process in CustomerMatchCondition.matches method. ");
return false;
}
}創(chuàng)建引用該Condition的配置類,
com.mirson.spring.boot.research.startup.CusomterConditional
@Configuration
@Conditional(CustomerMatchCondition.class)
@Log4j2
public class CusomterConditional {
public Object newObj() {
log.info("Process in CusomterConditional.newObj method.");
return new Object();
}
}啟動調試,分析調用棧:

可以看到, 先從第一步調用refresh調用容器初始化,再到第二步處理Bean配置定義信息, 最后調用注解的doScan掃描方法,這樣就能夠找到我們自定義的CustomerMatchCondition,調用Condtion定義的matches接口實現(xiàn), 決定是否要執(zhí)行CustomerConditional 的newObject方法。
4. Conditional核心之matches匹配接口
matchs方法是做規(guī)則校驗處理, SpringBootCondition源碼:
public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(getClass());
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 根據注解信息, 獲取類或方法名稱
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 獲取實現(xiàn)類的處理匹配結果
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 日志打印匹配結果
logOutcome(classOrMethodName, outcome);
// ConditionEvaluationReport中記錄處理結果信息
recordEvaluation(context, classOrMethodName, outcome);
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);
}
}
...
}- 獲取使用了Conditional的類或方法名稱信息。
- 根據Conditional條件規(guī)則判斷, 獲取返回處理結果。
- 判斷是否開啟日志記錄功能,打印處理結果。
- 記錄處理結果至ConditionEvaluationReport的outcomes屬性中。最后返回布爾值的處理結果。它是通過 ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法調用, 可以看到它是在Bean創(chuàng)建之前就先調用,歸屬Bean配置定義信息的邏輯處理,且在validate方法之前處理。調用機制要理解清楚,我們管理配置。
5. Conditional核心之條件化注解具體實現(xiàn)
以ConditionalOnBean為例, 進行分析, 源碼:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
...
}采用Conditional注解, 具體條件判斷邏輯在OnBeanCondition類中實現(xiàn), 源碼:
@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
/**
* Bean definition attribute name for factory beans to signal their product type (if
* known and it can't be deduced from the factory bean class).
*/
public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE;
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
...
}OnBeanCondition類的作用是判斷容器中有無指定的Bean實例, 如果存在, 則條件生效。
它實現(xiàn)了抽象類FilteringSpringBootCondition的getOutcomes方法,同時實現(xiàn)了SpringBootCondition的getMatchOutcome方法, 兩個核心方法接口,一個是獲取定義的匹配條件,一個是返回匹配的結果信息, OnBeanCondition子類去實現(xiàn)具體的判斷邏輯, 根據定義的條件輸出判斷結果。
getOutcomes方法
方法源碼:
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// 創(chuàng)建數組, 記錄自動化配置的類信息
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
// 遍歷處理
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
// 獲取具有ConditionalOnBean注解設置的Bean
Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
// 記錄outcomes, 條件配置信息
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
if (outcomes[i] == null) {
// 為空, 則降級獲取ConditionalOnSingleCandidate配置信息
Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
"ConditionalOnSingleCandidate");
outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
}
}
}
return outcomes;
}該方法作用是掃描在META-INF的spring.factories文件中定義的配置類, 檢測是否包含對應的條件標注,
也就是是否使用了@OnBeanCondition標注,存在則會記錄, 進入后續(xù)方法邏輯處理。
可以看到, 通過outcomes數組來記錄所有采用了Conditional的Autoconfiguration配置類。
擴展分析:
我們講解的OnBeanCondition只是其中一個條件注解, 跟蹤代碼分析, 同組的還有OnClassConditional和OnWebApplicationCondition條件注解,啟動處理順序是:
OnClassConditional->OnWebApplicationCondition->OnBeanCondition,
spring.factories中大部份配置的Autoconfiguration都是采用OnClassConditional來作依賴類的條件判斷。
getMatchOutcomes方法
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
// 判斷注解類型, ConditionalOnBean處理邏輯
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class);
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());
}
// 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());
}
// 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();
}
return ConditionOutcome.match(matchMessage);
}上面的getOutcomes方法記錄了需要匹配處理的條目,該方法是作具體判斷實現(xiàn)。 這里支持三種條件注解: ConditionalOnBean、ConditionalOnSingleCandidate和ConditionalOnMissingBean。實際內部邏輯都會調用getMatchingBeans方法。處理完成之后, 返回ConditionMessage對象,最后通過ConditionOutcome包裝返回處理結果。
getMatchingBeans方法
該方法是做具體檢測是否符合條件注解所配置的信息,主要包含三種類型判斷,
一種是Bean Type 也就是class類型, 第二種是annotation標注, 最后一種是Name屬性判斷。
protected final MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 判斷bean的搜尋策略, ANCESTORS為搜索所有父容器的上下文定義
if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS");
// 父容器轉換
beanFactory = (ConfigurableListableBeanFactory) parent;
}
MatchResult matchResult = new MatchResult();
// 判斷bean的搜尋策略, 是否為CURRENT當前上下文
boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader());
List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(beans.getIgnoredTypes(), typeExtractor,
beanFactory, context, considerHierarchy);
// 根據bean的類型遍歷判斷是否符合規(guī)則
for (String type : beans.getTypes()) {
// type類型的具體處理邏輯, 內部為嵌套調用
Collection<String> typeMatches = getBeanNamesForType(beanFactory, type, typeExtractor,
context.getClassLoader(), considerHierarchy);
typeMatches.removeAll(beansIgnoredByType);
if (typeMatches.isEmpty()) {
matchResult.recordUnmatchedType(type);
}
else {
matchResult.recordMatchedType(type, typeMatches);
}
}
// 根據bean的注解遍歷判斷是否符合規(guī)則
for (String annotation : beans.getAnnotations()) {
List<String> annotationMatches = Arrays.asList(
// Annotation類型的具體處理邏輯, 內部為嵌套調用
getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy));
annotationMatches.removeAll(beansIgnoredByType);
if (annotationMatches.isEmpty()) {
matchResult.recordUnmatchedAnnotation(annotation);
}
else {
matchResult.recordMatchedAnnotation(annotation, annotationMatches);
}
}
// 根據bean的名稱遍歷判斷是否符合規(guī)則
for (String beanName : beans.getNames()) {
if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
matchResult.recordMatchedName(beanName);
}
else {
matchResult.recordUnmatchedName(beanName);
}
}
return matchResult;
}
1) 首先會判斷搜尋策略,是否需要搜尋父容器上下文, 支持三種模式,CURRENT: 當前上下文; ANCESTORS: 所有父容器的上下文定義; ALL: 就是支持以上兩種搜尋策略。
2) 其次就是根據注解的定義信息, 按三種方式進行判斷, 內部按這三種, 類型、注解和名稱做處理,如果是父級搜索,會采用遞歸調用, 檢測是否存在, 進行匹配判斷。方法調用層級:
getBeanNamesForType(…) -》collectBeanNamesForType(…)
getBeanNamesForAnnotation(…) -》collectBeanNamesForAnnotation(…)
以上就是以ConditionalOnBean為例, 對ConditionOnXXX的實現(xiàn)原理做了剖析, SpringBootCondition的其他實現(xiàn)類還有很多, 本章只抽取代表性常見的條件注解作分析,大家有興趣可再研究其他條件注解的實現(xiàn)機制, 這里就不一一例舉。
6. 總結
基于Conditional條件的自動化配置, 從SpringBootCondition實現(xiàn)原理到OnBeanCondition、AutoConfigurationImportFilter的剖析, 綜合可以看出Spring Boot對于條件化注解的實現(xiàn), 無論從層次結構, 還是內部邏輯處理的關聯(lián)性, 都比較清晰明了,值得借鑒的是它的良好的擴展性設計,比如策略模式, 模板模式等,抽象類的合理運用設計, 沒有出現(xiàn)接口泛濫, 強耦合性等問題, 也便于Spring Boot后續(xù)版本的功能擴展。
到此這篇關于Spring Boot 詳細分析Conditional自動化配置注解的文章就介紹到這了,更多相關Spring Boot Conditional內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用JAVA實現(xiàn)高并發(fā)無鎖數據庫操作步驟分享
一個在線2k的游戲,每秒鐘并發(fā)都嚇死人。傳統(tǒng)的hibernate直接插庫基本上是不可行的。我就一步步推導出一個無鎖的數據庫操作,詳情看下文2013-11-11
Java httpclient請求form-data格式并設置boundary代碼實現(xiàn)方法
在 Java 開發(fā)中,經常會遇到需要使用 httpclient 發(fā)送 form-data 格式請求的場景,本文將詳細介紹如何正確地實現(xiàn)這一操作,包括數據格式示例、常見報錯及解決方法,本文通過實例代碼詳解講解,感興趣的朋友一起看看吧2025-01-01

