Springboot中的@Conditional注解詳解
一、@Conditional源碼
@Conditional 來(lái)源于 spring-context 包下的一個(gè)注解。Conditional中文是條件的意思,@Conditional注解它的作用是按照一定的條件進(jìn)行判斷,滿(mǎn)足條件給容器注冊(cè)bean。
通過(guò)他的注解內(nèi)部可以發(fā)現(xiàn),他就是一個(gè)純功能性注解,他并沒(méi)有依賴(lài)于其他注解,類(lèi)上只有三個(gè)元注解。
- @Target({ElementType.TYPE, ElementType.METHOD}) 使用范圍接口、類(lèi)、枚舉、注解、方法
- @Retention(RetentionPolicy.RUNTIME): @Retention是用來(lái)修飾注解的生命周期的,RetentionPolicy.RUNTIME代表的是不僅被保存到class文件中,jvm加載class文件之后,仍然存在;一直有效!
- @Documented: @Documented和@Deprecated注解長(zhǎng)得有點(diǎn)像,@Deprecated是用來(lái)標(biāo)注某個(gè)類(lèi)或者方法不建議再繼續(xù)使用,@Documented只能用在注解上,如果一個(gè)注解@B,被@Documented標(biāo)注,那么被@B修飾的類(lèi),生成Javadoc文檔時(shí),會(huì)顯示@B。
package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { Class<? extends Condition>[] value(); }
@Conditional 只有一個(gè)參數(shù),并且這個(gè)參數(shù)要求是繼承與 Condition 類(lèi),并且參數(shù)是個(gè)數(shù)組,也就是可以 傳多個(gè)的。 Condition 類(lèi)是一個(gè)函數(shù)式接口(只有一個(gè)方法的接口被稱(chēng)為函數(shù)式接口)。 matches 方法就是比較方法,如果為 true 則注入,如果為 false 則不注入。
@FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
二、@Conditional擴(kuò)展注解
而除了@Conditional注解外,springboot通過(guò)@Conditional注解又?jǐn)U展了很多注解出來(lái),如下@ConditionalOnBean、@ConditionalOnClass等等…
三、@Conditional實(shí)戰(zhàn)
(1)自定義Condition實(shí)現(xiàn)類(lèi)
public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String isOpen = environment.getProperty("systemLog.isOpen"); // 就算沒(méi)有設(shè)置systemLog.isOpen,那么isOpen就等于null,Boolean.valueOf對(duì)于null照樣會(huì)返回false的 return Boolean.valueOf(isOpen); } }
(2)自定義一個(gè)實(shí)體類(lèi),用于測(cè)試
public class TestBean1 { @Override public String toString() { return super.toString() + "--我是TestBean1"; } }
(3)添加配置類(lèi),并在類(lèi)上使用@Conditional
@Configuration @Conditional(MyCondition.class) public class Myconfig { @Bean public TestBean1 testBean1(){ return new TestBean1(); } }
(4)添加測(cè)試類(lèi)
@RestController public class CommonController { @Autowired(required = false) private Myconfig myconfig; @Autowired(required = false) private TestBean1 testBean1; @RequestMapping("/import") public void printImportBeanInfo() { System.out.println(myconfig); System.out.println(testBean1); } }
(5)啟動(dòng)測(cè)試: 訪(fǎng)問(wèn) //localhost:8080/import ,可見(jiàn)Myconfig類(lèi)并沒(méi)有注入到容器,按正常來(lái)說(shuō)被@Configuration修飾之后是會(huì)存放到容器當(dāng)中的,但是顯然因?yàn)锧Conditional判斷為false所以沒(méi)有注入到容器當(dāng)中。
通過(guò)這個(gè)測(cè)試不難發(fā)現(xiàn)假如@Bean所在的類(lèi)沒(méi)有注入到容器當(dāng)中,那么他也不會(huì)被注入到容器當(dāng)中。
(6)在 application.yml 當(dāng)中添加如下配置,然后再啟動(dòng)測(cè)試。
systemLog: isOpen: true
(7)@Conditional還可以應(yīng)用于方法上,我們可以讓他和@Bean注解來(lái)配合使用
@Configuration public class Myconfig { @Bean @Conditional(MyCondition.class) public TestBean1 testBean1(){ return new TestBean1(); } }
四、@Conditional多條件
前言中說(shuō),@Conditional注解傳入的是一個(gè)Class數(shù)組,存在多種條件類(lèi)的情況。
這種情況貌似判斷難度加深了,測(cè)試一波,新增新的條件類(lèi),實(shí)現(xiàn)的 matches 返回 false (這種寫(xiě)死返回false的方法純屬測(cè)試用,沒(méi)有實(shí)際意義O(∩_∩)O)
public class ObstinateCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return false; } }
@Configuration @Conditional({MyCondition.class,ObstinateCondition.class}) public class Myconfig { @Bean public TestBean1 testBean1(){ return new TestBean1(); } }
測(cè)試結(jié)果得知:
- 第一個(gè)條件類(lèi)實(shí)現(xiàn)的方法返回true,第二個(gè)返回false,則結(jié)果false,不注入進(jìn)容器。
- 第一個(gè)條件類(lèi)實(shí)現(xiàn)的方法返回true,第二個(gè)返回true,則結(jié)果true,注入進(jìn)容器中。
五、常見(jiàn)的擴(kuò)展注解
關(guān)于這些擴(kuò)展注解其實(shí)在官網(wǎng)源碼當(dāng)中是有注釋的,感興趣的可以看一下:
5.1.@ConditionalOnClass
主要是判斷是否存在這個(gè)類(lèi)文件,如果有這個(gè)文件就相當(dāng)于滿(mǎn)足條件,然后可以注入到容器當(dāng)中。
當(dāng)然并不是說(shuō)容器里面是否有這個(gè)類(lèi)哈,不要理解錯(cuò)了,這也就是我們有時(shí)候使用springboot只需要引入個(gè)依賴(lài),框架就可以用的原因!
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional({OnClassCondition.class}) public @interface ConditionalOnClass { // 必須出現(xiàn)的類(lèi) Class<?>[] value() default {}; // 必須存在的類(lèi)名,必須是全限類(lèi)名,也就是包含包名+類(lèi)名。 String[] name() default {}; }
用法示例:
@Configuration @ConditionalOnClass({TestBean2.class}) public class Myconfig { @Bean @ConditionalOnClass(name = "com.gzl.cn.springbootcache.config.TestBean3") public TestBean1 testBean1(){ return new TestBean1(); } }
5.2.@ConditionalOnMissingClass
@ConditionalOnMissingClass 只有一個(gè) value 屬性。他和 @ConditionalOnClass 功能正好相反,@ConditionalOnClass是class存在為true,而@ConditionalOnMissingClass是不存在為true,也就是存在為false。為fasle就意味著不注入。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional({OnClassCondition.class}) public @interface ConditionalOnMissingClass { // 必須不存在的類(lèi)名稱(chēng),全類(lèi)名 String[] value() default {}; }
@ConditionalOnMissingClass("com.gzl.cn.springbootcache.config.TestBean5")
或者
@ConditionalOnMissingClass(value = "com.gzl.cn.springbootcache.config.TestBean5")
5.3.@ConditionalOnBean
bean存在的時(shí)候注入,不存在的時(shí)候不注入,這塊就是指的spring的ioc容器了。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional({OnBeanCondition.class}) public @interface ConditionalOnBean { // 指定bean的類(lèi)類(lèi)型。當(dāng)所有指定類(lèi)的bean都包含在容器中時(shí),條件匹配。 Class<?>[] value() default {}; // 指定bean的全類(lèi)名。當(dāng)指定的所有類(lèi)的bean都包含在容器中時(shí),條件匹配。 String[] type() default {}; // bean所聲明的注解,當(dāng)ApplicationContext中存在聲明該注解的bean時(shí)返回true Class<? extends Annotation>[] annotation() default {}; // bean的id,當(dāng)ApplicationContext中存在給定id的bean時(shí)返回true,這個(gè)id指的就是容器當(dāng)中對(duì)象的id String[] name() default {}; // 搜索容器層級(jí),默認(rèn)是所有上下文搜索 SearchStrategy search() default SearchStrategy.ALL; // 可能在其泛型參數(shù)中包含指定bean類(lèi)型的其他類(lèi) Class<?>[] parameterizedContainer() default {}; }
(1)代碼示例:假如我把testBean2方法刪掉,那么testBean1也將會(huì)不注入。
@Configuration public class Myconfig { @Bean public TestBean2 testBean2(){ return new TestBean2(); } @Bean @ConditionalOnBean(TestBean2.class) public TestBean1 testBean1() { return new TestBean1(); } }
(2)測(cè)試類(lèi)
@RestController public class CommonController { @Autowired(required = false) private TestBean1 testBean1; @Autowired(required = false) private TestBean2 testBean2; @Autowired(required = false) private Myconfig myconfig; @RequestMapping("/import") public void printImportBeanInfo() { System.out.println(myconfig); System.out.println(testBean1); System.out.println(testBean2); } }
(3)運(yùn)行輸出結(jié)果:會(huì)發(fā)現(xiàn)三個(gè)對(duì)象是都注入到容器當(dāng)中了。
(4)目前存在的問(wèn)題
注意這里還存在一個(gè)執(zhí)行順序問(wèn)題,假如我把以下代碼放到啟動(dòng)類(lèi)當(dāng)中,而不是 Myconfig 配置文件當(dāng)中,這時(shí)候會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,testBean2注入進(jìn)去了,但是帶有 @ConditionalOnBean(TestBean2.class) 條件的 testBean1 沒(méi)有注入進(jìn)去。原因其實(shí)很簡(jiǎn)單,執(zhí)行 testBean1 的時(shí)候,testBean2并沒(méi)有注入進(jìn)去,然后testBean1就注入失敗了,緊接著失敗后testBean2又注入進(jìn)來(lái)了。
@Bean public TestBean2 testBean2(){ return new TestBean2(); }
(5)緊接著我又做了個(gè)試驗(yàn),把啟動(dòng)類(lèi)當(dāng)中的testBean2刪掉了,又創(chuàng)建了如下一個(gè)配置類(lèi),但是testBean1還是注入失敗。
@Configuration public class MyconfigTest { @Bean public TestBean2 testBean2(){ return new TestBean2(); } }
(6)想要解決這個(gè)問(wèn)題很簡(jiǎn)單,想辦法讓 MyconfigTest 比 Myconfig 先加載就可以了
于是我在MyconfigTest 添加了如下order注解
@Order(Ordered.HIGHEST_PRECEDENCE) //最高優(yōu)先級(jí)
然后在Myconfig 也添加了Order注解
@Order(Ordered.LOWEST_PRECEDENCE) //最低優(yōu)先級(jí)
但是仍然沒(méi)有解決該問(wèn)題。@Configuration并不能通過(guò)@Order指定順序。
(7)大膽猜測(cè)下: @Configuration通過(guò)配置類(lèi)名的自然順序來(lái)加載的。
將MyconfigTest改名字:還別說(shuō)改完名字真的就可以了!
(8)不可能每次遇到這種問(wèn)題都改名字吧,經(jīng)查文檔,終于找到了需要的東西:我們可以通過(guò)@AutoConfigureBefore,@AutoConfigureAfter來(lái)控制配置類(lèi)的加載順序。
在MyconfigTest類(lèi)上添加 @AutoConfigureBefore(Myconfig.class) ,意思是在Myconfig實(shí)例化之前加載。如果要讓@AutoConfigureBefore生效,還需要在META-INF/spring.factories文件中添加如下內(nèi)容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.gzl.cn.springbootcache.config.MyconfigTest,\ com.gzl.cn.springbootcache.config.Myconfig
最終成功解決哈!
5.4.@ConditionalOnMissingBean
bean不存在的時(shí)候注入,存在的時(shí)候?yàn)閒alse。跟@ConditionalOnBean正好是相反的。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional({OnBeanCondition.class}) public @interface ConditionalOnMissingBean { Class<?>[] value() default {}; String[] type() default {}; // 識(shí)別匹配 bean 時(shí),可以被忽略的 bean 的 class 類(lèi)型 Class<?>[] ignored() default {}; //識(shí)別匹配 bean 時(shí),可以被忽略的 bean 的 class 類(lèi)型名稱(chēng) String[] ignoredType() default {}; Class<? extends Annotation>[] annotation() default {}; String[] name() default {}; SearchStrategy search() default SearchStrategy.ALL; Class<?>[] parameterizedContainer() default {}; }
@ConditionalOnMissingBean 代表的是如果容器里面沒(méi)有 TestBean1 的實(shí)例,那么就運(yùn)行@Bean修飾的方法注入對(duì)象,不管注入的什么對(duì)象。
@Configuration public class Myconfig { @Bean @ConditionalOnMissingBean(TestBean1.class) public TestBean1 testBean1() { return new TestBean1(); } }
注意: @ConditionalOnMissingBean 和 @ConditionalOnBean 使用的時(shí)候是可以不帶任何屬性的,不帶任何屬性的時(shí)候他就是判斷的當(dāng)前注入的類(lèi)型。@ConditionalOnBean是判斷當(dāng)沒(méi)有的時(shí)候進(jìn)行注入,例如如下:他只會(huì)注入一個(gè)TestBean1進(jìn)去。
@Configuration public class Myconfig { @Bean @ConditionalOnMissingBean public TestBean1 testBean1() { System.out.println("1111111111"); return new TestBean1(); } @Bean @ConditionalOnMissingBean public TestBean1 testBean2() { System.out.println("2_222_222_222"); return new TestBean1(); } }
5.5.@ConditionalOnProperty
@ConditionalOnProperty 主要可用于通過(guò)和 springboot 當(dāng)中 application 配置文件來(lái)使用。在實(shí)戰(zhàn)當(dāng)中我們也可以通過(guò)他來(lái)實(shí)現(xiàn)配置化管理bean。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Conditional({OnPropertyCondition.class}) public @interface ConditionalOnProperty { // 指定的屬性完整名稱(chēng),不能和name同時(shí)使用。 String[] value() default {}; // //配置文件中屬性的前綴 String prefix() default ""; // //指定的屬性名稱(chēng) String[] name() default {}; // 指定的屬性的屬性值要等于該指定值,當(dāng)value或name為一個(gè)時(shí)使用 String havingValue() default ""; // 當(dāng)不匹配時(shí)是否允許加載,當(dāng)為true時(shí)就算不匹配也不影響bean的注入或配置類(lèi)的生效。 boolean matchIfMissing() default false; }
使用示例:
@Configuration @ConditionalOnProperty( prefix = "system.log", name = {"open"}, havingValue = "true", matchIfMissing = false ) public class Myconfig { @Bean public TestBean1 testBean1() { return new TestBean1(); } }
然后只要在application添加如下配置,Myconfig和testBean1就會(huì)注入到容器當(dāng)中。如果不設(shè)置也不會(huì)報(bào)錯(cuò),只是注入不到容器里而已。
system: log: open: true
5.6.其他注解
- @ConditionalOnJava:只有運(yùn)行指定版本的 Java 才會(huì)加載 Bean
- @ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication:只有運(yùn)行在(不在)web 應(yīng)用里才會(huì)加載這個(gè) bean
- @ConditionalOnCloudPlatform:只有運(yùn)行在指定的云平臺(tái)上才加載指定的 bean
- @ConditionalOnJndi:只有指定的資源通過(guò) JNDI 加載后才加載 bean
- @ConditionalOnExpression(“${test.express}==true”) :可通過(guò)spring提供的spEL表達(dá)式靈活配置,當(dāng)表達(dá)式為true的時(shí)候,才會(huì)實(shí)例化一個(gè)Bean
- @ConditionalOnSingleCandidate(UserService.class) :表示ioc容器中只有一個(gè)UserService類(lèi)型的Bean,才生效
- @ConditionalOnResource:指定的靜態(tài)資源?件存在 才加載
六、總結(jié)
到此這篇關(guān)于Springboot中的@Conditional注解詳解的文章就介紹到這了,更多相關(guān)@Conditional注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)MD5加密的方式與實(shí)例代碼
MD5加密是一種常見(jiàn)的加密方式,我們經(jīng)常用在保存用戶(hù)密碼和關(guān)鍵信息上。那么它到底有什么,又什么好處呢,會(huì)被這么廣泛的運(yùn)用在應(yīng)用開(kāi)發(fā)中2021-10-10Spring開(kāi)發(fā)核心之AOP的實(shí)現(xiàn)與切入點(diǎn)持久化
面向?qū)ο缶幊淌且环N編程方式,此編程方式的落地需要使用“類(lèi)”和 “對(duì)象”來(lái)實(shí)現(xiàn),所以,面向?qū)ο缶幊唐鋵?shí)就是對(duì) “類(lèi)”和“對(duì)象” 的使用,面向切面編程,簡(jiǎn)單的說(shuō),就是動(dòng)態(tài)地將代碼切入到類(lèi)的指定方法、指定位置上的編程思想就是面向切面的編程2022-10-10比較java中Future與FutureTask之間的關(guān)系
在本篇文章里我們給大家分享了java中Future與FutureTask之間的關(guān)系的內(nèi)容,有需要的朋友們可以跟著學(xué)習(xí)下。2018-10-10解決報(bào)錯(cuò):java:讀取jar包時(shí)出錯(cuò):error in opening zip 
文章總結(jié):解決Java讀取jar包時(shí)出錯(cuò)的問(wèn)題,通過(guò)下載源碼并刷新項(xiàng)目解決了問(wèn)題,希望對(duì)大家有所幫助2024-11-11關(guān)于ScheduledThreadPoolExecutor不執(zhí)行的原因分析
這篇文章主要介紹了關(guān)于ScheduledThreadPoolExecutor不執(zhí)行的原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Java編程long數(shù)據(jù)類(lèi)型的使用問(wèn)題
這篇文章主要介紹了Java編程數(shù)據(jù)類(lèi)型long的使用問(wèn)題,涉及長(zhǎng)整型數(shù)據(jù)的取值范圍和不同整數(shù)類(lèi)型的表示方法,需要的朋友可以參考下2017-09-09mybatis plus or and 的合并寫(xiě)法實(shí)例
這篇文章主要介紹了mybatis plus or and 的合并寫(xiě)法實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02