SpringBoot的@Conditional條件注解詳解
@Conditional注解
打開每個(gè)自動(dòng)配置類,都會(huì)看到@Conditional或其衍生的條件注解,本節(jié)我們來認(rèn)識(shí)下@Conditional注解。
認(rèn)識(shí)條件注解
@Conditional注解是由Spring4.0版本引入的新特性,可根據(jù)是否滿足指定的條件來決定是否進(jìn)行Bean的實(shí)例化裝配,比如設(shè)定類路徑下包含某個(gè)jar包的時(shí)候才會(huì)對(duì)注解的類進(jìn)行實(shí)例化操作??傊歉鶕?jù)一些特定條件來控制Bean實(shí)例化行為,@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的情況下,被注解的類才會(huì)被加載。上一篇文章講到的OnClassCondition類就是Condition的子類之一,相關(guān)代碼如下:
@FunctionalInterface public interface Condition { //決定條件是否匹配 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
matches方法的第一個(gè)參數(shù)為ConditionContext,可以通過接口提供的方法來獲取Spring應(yīng)用的上下文信息,ConditionContext接口定義如下:
public interface ConditionContext { //返回BeanDefinitionRegistry注冊(cè)表,可以檢查Bean的定義 BeanDefinitionRegistry getRegistry(); //ConfigurableListableBeanFactory ,可以檢查Bean是否已經(jīng)存在,進(jìn)一步檢查Bean屬性 @Nullable ConfigurableListableBeanFactory getBeanFactory(); //獲取Envirment,獲取當(dāng)前環(huán)境變量,監(jiān)測(cè)當(dāng)前環(huán)境變量是否存在 Environment getEnvironment(); //ResourceLoader ,用于讀取或檢查所加載的資源 ResourceLoader getResourceLoader(); //返回ClassLoader ,用于檢查類是否存在 @Nullable ClassLoader getClassLoader(); }
matches方法的第二個(gè)參數(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項(xiàng)目中提供了各類基于@Conditional注解的衍生注解,它們適用于不同的場(chǎng)景并提供了不同的功能。以下相關(guān)注解均位于spring-boot-auroconfigure項(xiàng)目的org.springframework.boot.autoconfigure.condition包下。
- @ConditionalOnBean:在容器中有指定Bean的條件下。
- @ConditionalOnClass:在classPath類路徑下有指定類的條件下。
- @ConditionalOnCloudPlatform:當(dāng)指定的平臺(tái)處于active狀態(tài)時(shí)。
- @ConditionalOnExpression:基于SpEL表達(dá)式的條件判斷。
- @ConditionalOnJava:基于JVM作為判斷條件。
- @ConditionalOnJndi:在JNDI存在的條件下查找指定的位置。
- @ConditionalOnMissingBean:當(dāng)容器中沒有指定Bean的條件時(shí)。
- @ConditionalOnMissingClass:當(dāng)類路徑下沒有指定類的條件時(shí)。
- @ConditionalOnNotWebApplication:在項(xiàng)目不是一個(gè)Web項(xiàng)目的條件下。
- @ConditionalOnProperty:在指定的屬性有指定的值。
- @ConditionalOnResource: 類路徑是否有指定的值。
- @ConditionalOnSingleCandidate: 在指定的Bean在容器中只有一個(gè)或者多個(gè)但是指定了首選的Bean時(shí)。
- @ConditionalOnWebApplication: 在項(xiàng)目是一個(gè)Web項(xiàng)目的條件下。
如果仔細(xì)觀察這些注解的源碼,會(huì)發(fā)現(xiàn)他們其實(shí)都組合了@Conditional注解,不同之處時(shí)他們中指定的條件(Condition)不同。下面以@ConditionalOnWebApplication為例對(duì)衍生注解進(jìn)行簡(jiǎ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注解,并且指定了對(duì)應(yīng)的Condition為OnWebApplicationCondition。OnWebApplicationCondition類的結(jié)構(gòu)與前面講到的OnClassCondition一樣,都繼承自SpringBootCondition并實(shí)現(xiàn)了AutoConfigurationImportFilter接口。下圖講述了以O(shè)nWebApplicationCondition為例衍生注解的關(guān)系結(jié)構(gòu),重點(diǎn)講述了Condition的功能和用法。
上面學(xué)習(xí)了Condition接口的源碼,抽象類SpringBootCondition是如何實(shí)現(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中實(shí)現(xiàn)類matches方法,而該方法中最核心的部分是通過調(diào)用新定義的抽象方法getMatchOutcome并交由子類來實(shí)現(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()); } ...... }
可以看出,是否匹配是由兩個(gè)條件決定的:被注解的類或方法是否包含ConditionalOnWebApplication注解,是否為web應(yīng)用。
- 如果包含ConditionalOnWebApplication注解,并且不是Web應(yīng)用,那么返回不匹配。
- 如果不包含ConditionalOnWebApplication注解,并且時(shí)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,如果配置屬性,會(huì)獲取type屬性對(duì)應(yīng)的值。 如果返回值為Type.SERVLET,調(diào)用isServletWebApplication方法來進(jìn)行判斷。該方法的判斷有以下條件:
- GenericWebApplicationContext類是否在類路徑下
- 容器內(nèi)是否存在注冊(cè)名為session的scope
- 容器的Environment是否為ConfigurableWebEnvironment
- 容器的ResourceLoader是否為WebApplicationContext
在完成以上判斷以后,得出的最終結(jié)果封裝為ConditionOutcome對(duì)象返回,并在抽象類SpringBootCondition的matches方法中完成判斷,返回最終結(jié)果。
到此這篇關(guān)于SpringBoot的@Conditional條件注解詳解的文章就介紹到這了,更多相關(guān)@Conditional條件注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java計(jì)算代碼段執(zhí)行時(shí)間的詳細(xì)代碼
java里計(jì)算代碼段執(zhí)行時(shí)間可以有兩種方法,一種是毫秒級(jí)別的計(jì)算,另一種是更精確的納秒級(jí)別的計(jì)算,這篇文章主要介紹了java計(jì)算代碼段執(zhí)行時(shí)間,需要的朋友可以參考下2022-08-08200行Java代碼如何實(shí)現(xiàn)依賴注入框架詳解
依賴注入對(duì)大家來說應(yīng)該都不陌生,下面這篇文章主要給大家介紹了關(guān)于利用200行Java代碼如何實(shí)現(xiàn)依賴注入框架的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05SpringCloud集成和使用OpenFeign的教程指南
在微服務(wù)架構(gòu)中,服務(wù)間的通信是至關(guān)重要的,SpringCloud作為一個(gè)功能強(qiáng)大的微服務(wù)框架,為我們提供了多種服務(wù)間通信的方式,其中,OpenFeign是一個(gè)聲明式的Web服務(wù)客戶端,它使得編寫Web服務(wù)客戶端變得更加簡(jiǎn)單,本文將詳細(xì)介紹如何在SpringCloud項(xiàng)目中集成和使用OpenFeign2024-10-10專屬于程序員的浪漫-Java輸出動(dòng)態(tài)閃圖iloveyou
這篇文章主要介紹了專屬于程序員的浪漫-Java輸出動(dòng)態(tài)閃圖iloveyou,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11springboot整合ehcache和redis實(shí)現(xiàn)多級(jí)緩存實(shí)戰(zhàn)案例
這篇文章主要介紹了springboot整合ehcache和redis實(shí)現(xiàn)多級(jí)緩存實(shí)戰(zhàn)案例,從源碼角度分析下多級(jí)緩存實(shí)現(xiàn)原理,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08SpringMVC互聯(lián)網(wǎng)軟件架構(gòu)REST使用詳解
這篇文章主要為大家詳細(xì)介紹了SpringMVC互聯(lián)網(wǎng)軟件架構(gòu)REST的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03解決javac不是內(nèi)部或外部命令,也不是可運(yùn)行程序的報(bào)錯(cuò)問題
在學(xué)著使用Java的命令行來編譯java文件的時(shí)候,遇到了這個(gè)問題,本文主要介紹了解決javac不是內(nèi)部或外部命令,也不是可運(yùn)行程序的報(bào)錯(cuò)問題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04