SpringBoot的@Conditional條件注解詳解
@Conditional注解
打開每個自動配置類,都會看到@Conditional或其衍生的條件注解,本節(jié)我們來認(rèn)識下@Conditional注解。
認(rèn)識條件注解
@Conditional注解是由Spring4.0版本引入的新特性,可根據(jù)是否滿足指定的條件來決定是否進(jìn)行Bean的實例化裝配,比如設(shè)定類路徑下包含某個jar包的時候才會對注解的類進(jìn)行實例化操作??傊?,是根據(jù)一些特定條件來控制Bean實例化行為,@Conditional注解代碼如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}@Conditional注解唯一的元素屬性是接口Condition的數(shù)組,只有在數(shù)組中指定的所有Condition的matches方法都返回true的情況下,被注解的類才會被加載。上一篇文章講到的OnClassCondition類就是Condition的子類之一,相關(guān)代碼如下:
@FunctionalInterface
public interface Condition {
//決定條件是否匹配
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}matches方法的第一個參數(shù)為ConditionContext,可以通過接口提供的方法來獲取Spring應(yīng)用的上下文信息,ConditionContext接口定義如下:
public interface ConditionContext {
//返回BeanDefinitionRegistry注冊表,可以檢查Bean的定義
BeanDefinitionRegistry getRegistry();
//ConfigurableListableBeanFactory ,可以檢查Bean是否已經(jīng)存在,進(jìn)一步檢查Bean屬性
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
//獲取Envirment,獲取當(dāng)前環(huán)境變量,監(jiān)測當(dāng)前環(huán)境變量是否存在
Environment getEnvironment();
//ResourceLoader ,用于讀取或檢查所加載的資源
ResourceLoader getResourceLoader();
//返回ClassLoader ,用于檢查類是否存在
@Nullable
ClassLoader getClassLoader();
}matches方法的第二個參數(shù)為AnnotatedTypeMetadata ,該接口提供了訪問特定類或方法的注解功能,并且不需要加載類,可以用來檢查帶有@Bean注解的方法上是否還有其他注解,AnnotatedTypeMetadata 接口定義如下:
public interface AnnotatedTypeMetadata {
MergedAnnotations getAnnotations();
default boolean isAnnotated(String annotationName) {
return this.getAnnotations().isPresent(annotationName);
}
@Nullable
default Map<String, Object> getAnnotationAttributes(String annotationName) {
return this.getAnnotationAttributes(annotationName, false);
}
@Nullable
default Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
MergedAnnotation<Annotation> annotation = this.getAnnotations().get(annotationName, (Predicate)null, MergedAnnotationSelectors.firstDirectlyDeclared());
return !annotation.isPresent() ? null : annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, true));
}
@Nullable
default MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName) {
return this.getAllAnnotationAttributes(annotationName, false);
}
@Nullable
default MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) {
MergedAnnotation.Adapt[] adaptations = Adapt.values(classValuesAsString, true);
return (MultiValueMap)this.getAnnotations().stream(annotationName).filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)).map(MergedAnnotation::withNonMergedAttributes).collect(MergedAnnotationCollectors.toMultiValueMap((map) -> {
return map.isEmpty() ? null : map;
}, adaptations));
}
}isAnnotated方法能夠提供判斷帶有@Bean注解的方法上是否還有其他注解的功能。其他方法提供不同形式的獲取@Bean注解的方法上其他注解的屬性信息。
條件注解的衍生注解
在Spring Boot的autoconfigure項目中提供了各類基于@Conditional注解的衍生注解,它們適用于不同的場景并提供了不同的功能。以下相關(guān)注解均位于spring-boot-auroconfigure項目的org.springframework.boot.autoconfigure.condition包下。

- @ConditionalOnBean:在容器中有指定Bean的條件下。
- @ConditionalOnClass:在classPath類路徑下有指定類的條件下。
- @ConditionalOnCloudPlatform:當(dāng)指定的平臺處于active狀態(tài)時。
- @ConditionalOnExpression:基于SpEL表達(dá)式的條件判斷。
- @ConditionalOnJava:基于JVM作為判斷條件。
- @ConditionalOnJndi:在JNDI存在的條件下查找指定的位置。
- @ConditionalOnMissingBean:當(dāng)容器中沒有指定Bean的條件時。
- @ConditionalOnMissingClass:當(dāng)類路徑下沒有指定類的條件時。
- @ConditionalOnNotWebApplication:在項目不是一個Web項目的條件下。
- @ConditionalOnProperty:在指定的屬性有指定的值。
- @ConditionalOnResource: 類路徑是否有指定的值。
- @ConditionalOnSingleCandidate: 在指定的Bean在容器中只有一個或者多個但是指定了首選的Bean時。
- @ConditionalOnWebApplication: 在項目是一個Web項目的條件下。
如果仔細(xì)觀察這些注解的源碼,會發(fā)現(xiàn)他們其實都組合了@Conditional注解,不同之處時他們中指定的條件(Condition)不同。下面以@ConditionalOnWebApplication為例對衍生注解進(jìn)行簡單的分析。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
//所需的web類型
Type type() default Type.ANY;
//可選應(yīng)用枚舉類
enum Type {
//任何類型
ANY,
//基于servlet的web應(yīng)用
SERVLET,
//基于reactive的web應(yīng)用
REACTIVE
}
}@ConditionalOnWebApplication注解的源碼中組合了@Conditional注解,并且指定了對應(yīng)的Condition為OnWebApplicationCondition。OnWebApplicationCondition類的結(jié)構(gòu)與前面講到的OnClassCondition一樣,都繼承自SpringBootCondition并實現(xiàn)了AutoConfigurationImportFilter接口。下圖講述了以O(shè)nWebApplicationCondition為例衍生注解的關(guān)系結(jié)構(gòu),重點講述了Condition的功能和用法。

上面學(xué)習(xí)了Condition接口的源碼,抽象類SpringBootCondition是如何實現(xiàn)該方法的呢?相關(guān)源碼如下:
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 {
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
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);
}
}
......
/**
* Determine the outcome of the match along with suitable log output.
* @param context the condition context
* @param metadata the annotation metadata
* @return the condition outcome
*/
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
......
}在抽象類SpringBootCondition中實現(xiàn)類matches方法,而該方法中最核心的部分是通過調(diào)用新定義的抽象方法getMatchOutcome并交由子類來實現(xiàn),在matches方法中根據(jù)子類返回的結(jié)果判斷是否匹配。
下面來看下OnWebApplicationCondition的源代碼。
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {
......
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
ConditionOutcome outcome = isWebApplication(context, metadata, required);
if (required && !outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
if (!required && outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
return ConditionOutcome.match(outcome.getConditionMessage());
}
......
}可以看出,是否匹配是由兩個條件決定的:被注解的類或方法是否包含ConditionalOnWebApplication注解,是否為web應(yīng)用。
- 如果包含ConditionalOnWebApplication注解,并且不是Web應(yīng)用,那么返回不匹配。
- 如果不包含ConditionalOnWebApplication注解,并且時Web應(yīng)用,那么返回不匹配。
- 其他情況返回匹配。下面以SERVLET Web應(yīng)用為例,看相關(guān)源碼如何判斷是否為web應(yīng)用的。
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {
private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";
......
//推斷web應(yīng)用是否匹配
private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
boolean required) {
switch (deduceType(metadata)) {
case SERVLET:
return isServletWebApplication(context);
case REACTIVE:
return isReactiveWebApplication(context);
default:
return isAnyWebApplication(context, required);
}
}
......
private ConditionOutcome isServletWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
//判斷常量定義是否存在
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
}
//判斷BeanFactory是否存在
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match(message.foundExactly("'session' scope"));
}
}
//判斷Enviroment的類型是否為ConfigurableWebEnvironment類型
if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
}
//判斷ResourceLoader的類型是否為WebApplicationContext
if (context.getResourceLoader() instanceof WebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
}
return ConditionOutcome.noMatch(message.because("not a servlet web application"));
}
......
//從AnnotateTypeMeatdata中獲取type值
private Type deduceType(AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
if (attributes != null) {
return (Type) attributes.get("type");
}
return Type.ANY;
}
}首先在isWebApplication方法中進(jìn)行Web應(yīng)用類型的推斷。這里使用AnnotatedTypeMetadata的getAnnotationAttributes方法獲取所有關(guān)于@ConditionalOnWebApplication的注解屬性。返回值為null說明未配置任何屬性,默認(rèn)為Type.ANY,如果配置屬性,會獲取type屬性對應(yīng)的值。 如果返回值為Type.SERVLET,調(diào)用isServletWebApplication方法來進(jìn)行判斷。該方法的判斷有以下條件:
- GenericWebApplicationContext類是否在類路徑下
- 容器內(nèi)是否存在注冊名為session的scope
- 容器的Environment是否為ConfigurableWebEnvironment
- 容器的ResourceLoader是否為WebApplicationContext
在完成以上判斷以后,得出的最終結(jié)果封裝為ConditionOutcome對象返回,并在抽象類SpringBootCondition的matches方法中完成判斷,返回最終結(jié)果。
到此這篇關(guān)于SpringBoot的@Conditional條件注解詳解的文章就介紹到這了,更多相關(guān)@Conditional條件注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud集成和使用OpenFeign的教程指南
在微服務(wù)架構(gòu)中,服務(wù)間的通信是至關(guān)重要的,SpringCloud作為一個功能強大的微服務(wù)框架,為我們提供了多種服務(wù)間通信的方式,其中,OpenFeign是一個聲明式的Web服務(wù)客戶端,它使得編寫Web服務(wù)客戶端變得更加簡單,本文將詳細(xì)介紹如何在SpringCloud項目中集成和使用OpenFeign2024-10-10
專屬于程序員的浪漫-Java輸出動態(tài)閃圖iloveyou
這篇文章主要介紹了專屬于程序員的浪漫-Java輸出動態(tài)閃圖iloveyou,具有一定參考價值,需要的朋友可以了解下。2017-11-11
springboot整合ehcache和redis實現(xiàn)多級緩存實戰(zhàn)案例
這篇文章主要介紹了springboot整合ehcache和redis實現(xiàn)多級緩存實戰(zhàn)案例,從源碼角度分析下多級緩存實現(xiàn)原理,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
SpringMVC互聯(lián)網(wǎng)軟件架構(gòu)REST使用詳解
這篇文章主要為大家詳細(xì)介紹了SpringMVC互聯(lián)網(wǎng)軟件架構(gòu)REST的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
解決javac不是內(nèi)部或外部命令,也不是可運行程序的報錯問題
在學(xué)著使用Java的命令行來編譯java文件的時候,遇到了這個問題,本文主要介紹了解決javac不是內(nèi)部或外部命令,也不是可運行程序的報錯問題,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04

