springboot注解之@Conditional使用解析
前言
conditional 這個英文單詞翻譯過來是有條件的,所以 @Conditional 注解是作為條件存在的,如果滿足配置的條件則執(zhí)行,如果沒有滿足的話就不執(zhí)行。
一、@Conditional
@Conditional 注解上面說了是作為條件執(zhí)行的,那么是作為什么條件呢?這我們就需要知道 @Conditional 主要是作用在什么上面。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { Class<? extends Condition>[] value(); }
上面是 @Conditional 注解的源碼。我們看到注解的作用域是類、方法上。既然是作用在類上,即可以大體猜測到與 IOC 容器添加 bean 有關(guān)系。 事實上 @Conditional 注解一般與 @Configuration、@Bean 共同使用,也可以與 @Controller、@Service、@Component、@Repository 等等這些注解一起使用。所以 @Conditional 通常是作為是否添加這個對象為 IOC 容器組件的條件出現(xiàn)的。 既然知道 @Conditional 注解的作用,那么該注解應(yīng)該如何使用呢?我們看到該注解有一個必填的屬性 value,value 屬性的類型是 Condition 接口的實現(xiàn)類數(shù)組。我們看下 Condition 接口的源碼。
@FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
Condition 接口只有一個抽象方法,返回 boolean 類型,可以知道返回為 true 則條件成立,返回 false 條件不成立。 方法中有兩個參數(shù),context 中包含容器、bean 工廠、類加載器、資源加載器、環(huán)境配置這五大核心屬性獲取方法,metadata 是注解的信息。 從這里我們也可以自己實現(xiàn) matches 方法,去自定義一個條件類。 自己去實現(xiàn) Condition 接口比較復(fù)雜,那么有沒有一些已經(jīng)實現(xiàn)好的常用的一些類呢?springboot 給我們提供了大量的這樣的實現(xiàn)類,讓我們基本不用自己去實現(xiàn) Condition 接口,就可以滿足日常的開發(fā)。
二、@Conditional 的實現(xiàn)子注解
springboot 提供了大量的 @Conditional 子注解供我們使用,我們只需知道有哪些常用的子注解供我們使用即可。
springboot 提供的 @Conditional 子注解有:
- @ConditionalOnBean
- @ConditionalOnClass
- @ConditionalOnCloudPlatform
- @ConditionalOnExpression
- @ConditionalOnJava
- @ConditionalOnJndi
- @ConditionalOnMissingBean
- @ConditionalOnMissingClass
- @ConditionalOnNotWebApplication
- @ConditionalOnProperty
- @ConditionalOnResource
- @ConditionalOnSingleCandidate
- @ConditionalOnWarDeployment
- @ConditionalOnWebApplication
這些子注解是如何實現(xiàn)的呢?我們以 @ConditionalOnBean 注解為例看下源碼:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional({OnBeanCondition.class}) public @interface ConditionalOnBean { Class<?>[] value() default {}; String[] type() default {}; Class<? extends Annotation>[] annotation() default {}; String[] name() default {}; SearchStrategy search() default SearchStrategy.ALL; Class<?>[] parameterizedContainer() default {}; }
可以看到 OnBeanCondition 是 Condition 接口的具體實現(xiàn)類。 我們挑選一些日常常用的子注解做具體的說明。
三、@ConditionalOnClass 注解
@ConditionalOnClass 的意思是以是否有該類為為條件。我們以 springboot 自動配置 aop 的配置類做例子進行解讀。 我們打開 springboot 的自動配置包 spring-boot-autoconfigure-2.7.0.jar 包,找到 AopAutoConfiguration.class,打開后有這么一段代碼。
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({Advice.class}) static class AspectJAutoProxyingConfiguration { AspectJAutoProxyingConfiguration() { } @Configuration( proxyBeanMethods = false ) @EnableAspectJAutoProxy( proxyTargetClass = true ) @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true ) static class CglibAutoProxyConfiguration { CglibAutoProxyConfiguration() { } } @Configuration( proxyBeanMethods = false ) @EnableAspectJAutoProxy( proxyTargetClass = false ) @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "false" ) static class JdkDynamicAutoProxyConfiguration { JdkDynamicAutoProxyConfiguration() { } } }
我們看到在內(nèi)部類 AspectJAutoProxyingConfiguration 上標注了 @ConditionalOnClass({Advice.class}) 表示如果有 Advice.class 這個類則 AspectJAutoProxyingConfiguration 生效,否則就不生效。
那么我們測試以下。 首先我們測試以下沒有的時候。
@SpringBootApplication public class SpringbootFunctionApplication { public static void main(String[] args) { // 返回 IOC 容器 ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args); // 測試沒有 Advice.class 的時候,是否有 AspectJAutoProxyingConfiguration 組件 try { // 反射獲取內(nèi)部類的 Class 對象 Class<?> clazz = Class.forName("org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration"); System.out.println(MessageFormat.format("容器中 AspectJAutoProxyingConfiguration 類型的組件個數(shù)有:{0}", run.getBeanNamesForType(clazz).length)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
結(jié)果為:
容器中 AspectJAutoProxyingConfiguration 類型的組件個數(shù)有:0
可以看到如果沒有 Advice.class 的時候,@ConditionalOnClass 標簽做了攔截,沒有添加 AspectJAutoProxyingConfiguration 為組件。
我們看下如果導(dǎo)入 Advice.class 之后會有什么現(xiàn)象。
我們在 pom 文件中導(dǎo)入 aspectj 依賴
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.19</version> <scope>runtime</scope> </dependency>
最后執(zhí)行我們看到的結(jié)果為:
容器中 AspectJAutoProxyingConfiguration 類型的組件個數(shù)有:1
可以看到有了 Advice.class 之后,在容器添加 AspectJAutoProxyingConfiguration 作為 bean 的時候,通過了 @ConditionalOnClass 的校驗。
四、@ConditionalOnMissingClass 注解
我們說完了 @ConditionalOnClass,我們說一下它的相反意思的注解:@ConditionalOnMissingClass。 @ConditionalOnMissingClass 是如果沒有這個 class 類則執(zhí)行。我們同樣以 springboot 自動配置 aop 的配置類做例子,不同的是我們選擇了 AopAutoConfiguration.class 中的另一個內(nèi)部類 ClassProxyingConfiguration。
@Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass({"org.aspectj.weaver.Advice"}) @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true ) static class ClassProxyingConfiguration { ClassProxyingConfiguration() { } @Bean static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() { return (beanFactory) -> { if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory; AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } }; } }
可以看到內(nèi)部類 ClassProxyingConfiguration 上面標注了 @ConditionalOnMissingClass 標簽,條件是 org.aspectj.weaver.Advice 字符串,代表沒有該類則通過。
上面已經(jīng)引入了 aspectjweaver jar 包,我們看看容器中有沒有 ClassProxyingConfiguration 這個 bean。
@SpringBootApplication public class SpringbootFunctionApplication { public static void main(String[] args) { // 返回 IOC 容器 ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args); // 測試有 Advice.class 的時候,是否有 ClassProxyingConfiguration 組件 try { Class<?> clazz = Class.forName("org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration"); System.out.println(MessageFormat.format("容器中 ClassProxyingConfiguration 類型的組件個數(shù)有:{0}", run.getBeanNamesForType(clazz).length)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
結(jié)果為:
容器中 ClassProxyingConfiguration 類型的組件個數(shù)有:0
可以看出有 Advice.class 的時候 @ConditionalOnMissingClass 注解會判斷不通過,不會注冊 ClassProxyingConfiguration 為 bean。
反過來如果沒有 Advice.class 的時候會是什么樣子呢?
容器中 ClassProxyingConfiguration 類型的組件個數(shù)有:1
五、@ConditionalOnBean 與 @ConditionalOnMissingBean
上面我們看了注解 @ConditionalOnClass、@ConditionalOnMissingClass,我們將要看的注解 @ConditionalOnBean 與 @ConditionalOnMissingBean 其實意思也是相似的,只不過前兩個注解以是否有 class 類作為判斷條件,后兩個注解以容器中是否有組件 bean 作為判斷條件。 我們看一個例子
@Bean @ConditionalOnBean({MultipartResolver.class}) @ConditionalOnMissingBean(name = {"multipartResolver"}) public MultipartResolver multipartResolver(MultipartResolver resolver) { return resolver; }
這里 @ConditionalOnBean({MultipartResolver.class}) 如果容器中有 MultipartResolver 類型的 bean 則條件通過。 @ConditionalOnMissingBean(name = {“multipartResolver”}) 則表示如果容器中沒有 ID 為 multipartResolver 的 bean 則條件通過。 可以看出這段代碼的功能是,如果容器中有 MultipartResolver 這個類型的 bean 但 ID 名稱不是 multipartResolver,則把名稱改為 multipartResolver。
六、@ConditionalOnProperty 注解
@ConditionalOnProperty 注解是以項目中的配置作為條件確定是否注冊為 bean。 我們還是以 springboot 自動配置 aop 的配置類 AopAutoConfiguration.class 中的子類 AspectJAutoProxyingConfiguration 做例子進行解讀。
static class AspectJAutoProxyingConfiguration { AspectJAutoProxyingConfiguration() { } @Configuration( proxyBeanMethods = false ) @EnableAspectJAutoProxy( proxyTargetClass = true ) @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true ) static class CglibAutoProxyConfiguration { CglibAutoProxyConfiguration() { } } @Configuration( proxyBeanMethods = false ) @EnableAspectJAutoProxy( proxyTargetClass = false ) @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "false" ) static class JdkDynamicAutoProxyConfiguration { JdkDynamicAutoProxyConfiguration() { } } }
可以看到 @ConditionalOnProperty 注解具有4個屬性 prefix(配置前綴)、name(配置名稱)、havingValue(匹配的值)、matchIfMissing(如果找不到這個配置的值則返回) 在 AspectJAutoProxyingConfiguration 中有兩個內(nèi)部類 CglibAutoProxyConfiguration、JdkDynamicAutoProxyConfiguration。 CglibAutoProxyConfiguration 上面標注的注解 @ConditionalOnProperty 屬性匹配的值為 true,如果沒有該配置則默認通過。 JdkDynamicAutoProxyConfiguration 上面標注的注解 @ConditionalOnProperty 屬性匹配的值為 false,沒有 matchIfMissing 屬性。 我們進行下面測試:
@SpringBootApplication(scanBasePackages = {"com.study.springbootfunction", "com.study.exclude"}) public class SpringbootFunctionApplication { public static void main(String[] args) { // 返回 IOC 容器 ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args); // 是否包含 CglibAutoProxyConfiguration、JdkDynamicAutoProxyConfiguration try { Class<?> a = Class.forName("org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration"); Class<?> j = Class.forName("org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$JdkDynamicAutoProxyConfiguration"); System.out.println(MessageFormat.format("容器中 CglibAutoProxyConfiguration 類型的組件個數(shù)有:{0}", run.getBeanNamesForType(a).length)); System.out.println(MessageFormat.format("容器中 JdkDynamicAutoProxyConfiguration 類型的組件個數(shù)有:{0}", run.getBeanNamesForType(j).length)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
先不在項目中配置 spring.aop.proxy-target-class 運行結(jié)果為:
容器中 CglibAutoProxyConfiguration 類型的組件個數(shù)有:1
容器中 JdkDynamicAutoProxyConfiguration 類型的組件個數(shù)有:0
能夠得出 如果屬性 matchIfMissing 不配置的話默認為 false。 在項目中配置 spring.aop.proxy-target-class=true
spring: aop: proxy-target-class: true
運行結(jié)果為:
容器中 CglibAutoProxyConfiguration 類型的組件個數(shù)有:1
容器中 JdkDynamicAutoProxyConfiguration 類型的組件個數(shù)有:0
如果在項目中配置 spring.aop.proxy-target-class=false
spring: aop: proxy-target-class: false
運行結(jié)果為:
容器中 CglibAutoProxyConfiguration 類型的組件個數(shù)有:0
容器中 JdkDynamicAutoProxyConfiguration 類型的組件個數(shù)有:1
可以看出 @ConditionalOnProperty 屬性 havingValue 的值與項目中配置的值相匹配時為滿足條件,如果值不匹配的時候則條件不滿足。
七、@ConditionalOnWebApplication
如果是 web 應(yīng)用,則滿足條件。這個配置在 mvc 中的配置比較多,我們看下 mvc 中 DispatcherServlet 的自動配置類。
@AutoConfigureOrder(-2147483648) @AutoConfiguration( after = {ServletWebServerFactoryAutoConfiguration.class} ) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({DispatcherServlet.class}) public class DispatcherServletAutoConfiguration { }
我們看到 DispatcherServletAutoConfiguration 在是 servlet 的傳統(tǒng)項目中則成立,滿足條件。
到此這篇關(guān)于springboot注解之@Conditional使用解析的文章就介紹到這了,更多相關(guān)springboot注解@Conditional內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深層剖析java應(yīng)用開發(fā)中MyBayis緩存
這篇文章主要為大家深層剖析java開發(fā)中MyBayis緩存,文中講解了Mybatis緩存的分類以及使用的方式,有需要的朋友可以借鑒參考下,希望可以有所幫助2021-09-09Spring?MVC中@Controller和@RequestMapping注解使用
這篇文章主要介紹了Spring?MVC中@Controller和@RequestMapping注解使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02SpringCloud Config分布式配置中心使用教程介紹
springcloud config是一個解決分布式系統(tǒng)的配置管理方案。它包含了 client和server兩個部分,server端提供配置文件的存儲、以接口的形式將配置文件的內(nèi)容提供出去,client端通過接口獲取數(shù)據(jù)、并依據(jù)此數(shù)據(jù)初始化自己的應(yīng)用2022-12-12TCP/IP協(xié)議中三次握手四次揮手的原理及流程分析
這篇文章主要介紹了TCP/IP協(xié)議中三次握手四次揮手的原理及流程分析,具有一定參考價值,需要的朋友可以了解下。2017-11-11