Springboot中的@Conditional注解詳解
一、@Conditional源碼
@Conditional 來源于 spring-context 包下的一個注解。Conditional中文是條件的意思,@Conditional注解它的作用是按照一定的條件進行判斷,滿足條件給容器注冊bean。
通過他的注解內部可以發(fā)現,他就是一個純功能性注解,他并沒有依賴于其他注解,類上只有三個元注解。
- @Target({ElementType.TYPE, ElementType.METHOD}) 使用范圍接口、類、枚舉、注解、方法
- @Retention(RetentionPolicy.RUNTIME): @Retention是用來修飾注解的生命周期的,RetentionPolicy.RUNTIME代表的是不僅被保存到class文件中,jvm加載class文件之后,仍然存在;一直有效!
- @Documented: @Documented和@Deprecated注解長得有點像,@Deprecated是用來標注某個類或者方法不建議再繼續(xù)使用,@Documented只能用在注解上,如果一個注解@B,被@Documented標注,那么被@B修飾的類,生成Javadoc文檔時,會顯示@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 只有一個參數,并且這個參數要求是繼承與 Condition 類,并且參數是個數組,也就是可以 傳多個的。 Condition 類是一個函數式接口(只有一個方法的接口被稱為函數式接口)。 matches 方法就是比較方法,如果為 true 則注入,如果為 false 則不注入。
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}二、@Conditional擴展注解
而除了@Conditional注解外,springboot通過@Conditional注解又擴展了很多注解出來,如下@ConditionalOnBean、@ConditionalOnClass等等…


三、@Conditional實戰(zhàn)
(1)自定義Condition實現類
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String isOpen = environment.getProperty("systemLog.isOpen");
// 就算沒有設置systemLog.isOpen,那么isOpen就等于null,Boolean.valueOf對于null照樣會返回false的
return Boolean.valueOf(isOpen);
}
}(2)自定義一個實體類,用于測試
public class TestBean1 {
@Override
public String toString() {
return super.toString() + "--我是TestBean1";
}
}(3)添加配置類,并在類上使用@Conditional
@Configuration
@Conditional(MyCondition.class)
public class Myconfig {
@Bean
public TestBean1 testBean1(){
return new TestBean1();
}
}(4)添加測試類
@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)啟動測試: 訪問 //localhost:8080/import ,可見Myconfig類并沒有注入到容器,按正常來說被@Configuration修飾之后是會存放到容器當中的,但是顯然因為@Conditional判斷為false所以沒有注入到容器當中。

通過這個測試不難發(fā)現假如@Bean所在的類沒有注入到容器當中,那么他也不會被注入到容器當中。
(6)在 application.yml 當中添加如下配置,然后再啟動測試。
systemLog: isOpen: true

(7)@Conditional還可以應用于方法上,我們可以讓他和@Bean注解來配合使用
@Configuration
public class Myconfig {
@Bean
@Conditional(MyCondition.class)
public TestBean1 testBean1(){
return new TestBean1();
}
}四、@Conditional多條件
前言中說,@Conditional注解傳入的是一個Class數組,存在多種條件類的情況。
這種情況貌似判斷難度加深了,測試一波,新增新的條件類,實現的 matches 返回 false (這種寫死返回false的方法純屬測試用,沒有實際意義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();
}
}測試結果得知:
- 第一個條件類實現的方法返回true,第二個返回false,則結果false,不注入進容器。
- 第一個條件類實現的方法返回true,第二個返回true,則結果true,注入進容器中。
五、常見的擴展注解
關于這些擴展注解其實在官網源碼當中是有注釋的,感興趣的可以看一下:
5.1.@ConditionalOnClass
主要是判斷是否存在這個類文件,如果有這個文件就相當于滿足條件,然后可以注入到容器當中。
當然并不是說容器里面是否有這個類哈,不要理解錯了,這也就是我們有時候使用springboot只需要引入個依賴,框架就可以用的原因!
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
// 必須出現的類
Class<?>[] value() default {};
// 必須存在的類名,必須是全限類名,也就是包含包名+類名。
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 只有一個 value 屬性。他和 @ConditionalOnClass 功能正好相反,@ConditionalOnClass是class存在為true,而@ConditionalOnMissingClass是不存在為true,也就是存在為false。為fasle就意味著不注入。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnMissingClass {
// 必須不存在的類名稱,全類名
String[] value() default {};
}@ConditionalOnMissingClass("com.gzl.cn.springbootcache.config.TestBean5")或者
@ConditionalOnMissingClass(value = "com.gzl.cn.springbootcache.config.TestBean5")
5.3.@ConditionalOnBean
bean存在的時候注入,不存在的時候不注入,這塊就是指的spring的ioc容器了。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
// 指定bean的類類型。當所有指定類的bean都包含在容器中時,條件匹配。
Class<?>[] value() default {};
// 指定bean的全類名。當指定的所有類的bean都包含在容器中時,條件匹配。
String[] type() default {};
// bean所聲明的注解,當ApplicationContext中存在聲明該注解的bean時返回true
Class<? extends Annotation>[] annotation() default {};
// bean的id,當ApplicationContext中存在給定id的bean時返回true,這個id指的就是容器當中對象的id
String[] name() default {};
// 搜索容器層級,默認是所有上下文搜索
SearchStrategy search() default SearchStrategy.ALL;
// 可能在其泛型參數中包含指定bean類型的其他類
Class<?>[] parameterizedContainer() default {};
}(1)代碼示例:假如我把testBean2方法刪掉,那么testBean1也將會不注入。
@Configuration
public class Myconfig {
@Bean
public TestBean2 testBean2(){
return new TestBean2();
}
@Bean
@ConditionalOnBean(TestBean2.class)
public TestBean1 testBean1() {
return new TestBean1();
}
}(2)測試類
@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)運行輸出結果:會發(fā)現三個對象是都注入到容器當中了。
(4)目前存在的問題
注意這里還存在一個執(zhí)行順序問題,假如我把以下代碼放到啟動類當中,而不是 Myconfig 配置文件當中,這時候會發(fā)現一個問題,testBean2注入進去了,但是帶有 @ConditionalOnBean(TestBean2.class) 條件的 testBean1 沒有注入進去。原因其實很簡單,執(zhí)行 testBean1 的時候,testBean2并沒有注入進去,然后testBean1就注入失敗了,緊接著失敗后testBean2又注入進來了。
@Bean
public TestBean2 testBean2(){
return new TestBean2();
}(5)緊接著我又做了個試驗,把啟動類當中的testBean2刪掉了,又創(chuàng)建了如下一個配置類,但是testBean1還是注入失敗。
@Configuration
public class MyconfigTest {
@Bean
public TestBean2 testBean2(){
return new TestBean2();
}
}(6)想要解決這個問題很簡單,想辦法讓 MyconfigTest 比 Myconfig 先加載就可以了
于是我在MyconfigTest 添加了如下order注解
@Order(Ordered.HIGHEST_PRECEDENCE) //最高優(yōu)先級
然后在Myconfig 也添加了Order注解
@Order(Ordered.LOWEST_PRECEDENCE) //最低優(yōu)先級
但是仍然沒有解決該問題。@Configuration并不能通過@Order指定順序。
(7)大膽猜測下: @Configuration通過配置類名的自然順序來加載的。

將MyconfigTest改名字:還別說改完名字真的就可以了!

(8)不可能每次遇到這種問題都改名字吧,經查文檔,終于找到了需要的東西:我們可以通過@AutoConfigureBefore,@AutoConfigureAfter來控制配置類的加載順序。
在MyconfigTest類上添加 @AutoConfigureBefore(Myconfig.class) ,意思是在Myconfig實例化之前加載。如果要讓@AutoConfigureBefore生效,還需要在META-INF/spring.factories文件中添加如下內容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.gzl.cn.springbootcache.config.MyconfigTest,\ com.gzl.cn.springbootcache.config.Myconfig

最終成功解決哈!
5.4.@ConditionalOnMissingBean
bean不存在的時候注入,存在的時候為false。跟@ConditionalOnBean正好是相反的。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
Class<?>[] value() default {};
String[] type() default {};
// 識別匹配 bean 時,可以被忽略的 bean 的 class 類型
Class<?>[] ignored() default {};
//識別匹配 bean 時,可以被忽略的 bean 的 class 類型名稱
String[] ignoredType() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
Class<?>[] parameterizedContainer() default {};
}@ConditionalOnMissingBean 代表的是如果容器里面沒有 TestBean1 的實例,那么就運行@Bean修飾的方法注入對象,不管注入的什么對象。
@Configuration
public class Myconfig {
@Bean
@ConditionalOnMissingBean(TestBean1.class)
public TestBean1 testBean1() {
return new TestBean1();
}
}注意: @ConditionalOnMissingBean 和 @ConditionalOnBean 使用的時候是可以不帶任何屬性的,不帶任何屬性的時候他就是判斷的當前注入的類型。@ConditionalOnBean是判斷當沒有的時候進行注入,例如如下:他只會注入一個TestBean1進去。
@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 主要可用于通過和 springboot 當中 application 配置文件來使用。在實戰(zhàn)當中我們也可以通過他來實現配置化管理bean。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
// 指定的屬性完整名稱,不能和name同時使用。
String[] value() default {};
// //配置文件中屬性的前綴
String prefix() default "";
// //指定的屬性名稱
String[] name() default {};
// 指定的屬性的屬性值要等于該指定值,當value或name為一個時使用
String havingValue() default "";
// 當不匹配時是否允許加載,當為true時就算不匹配也不影響bean的注入或配置類的生效。
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就會注入到容器當中。如果不設置也不會報錯,只是注入不到容器里而已。
system:
log:
open: true5.6.其他注解
- @ConditionalOnJava:只有運行指定版本的 Java 才會加載 Bean
- @ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication:只有運行在(不在)web 應用里才會加載這個 bean
- @ConditionalOnCloudPlatform:只有運行在指定的云平臺上才加載指定的 bean
- @ConditionalOnJndi:只有指定的資源通過 JNDI 加載后才加載 bean
- @ConditionalOnExpression(“${test.express}==true”) :可通過spring提供的spEL表達式靈活配置,當表達式為true的時候,才會實例化一個Bean
- @ConditionalOnSingleCandidate(UserService.class) :表示ioc容器中只有一個UserService類型的Bean,才生效
- @ConditionalOnResource:指定的靜態(tài)資源?件存在 才加載
六、總結

到此這篇關于Springboot中的@Conditional注解詳解的文章就介紹到這了,更多相關@Conditional注解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決報錯:java:讀取jar包時出錯:error in opening zip 
文章總結:解決Java讀取jar包時出錯的問題,通過下載源碼并刷新項目解決了問題,希望對大家有所幫助2024-11-11
關于ScheduledThreadPoolExecutor不執(zhí)行的原因分析
這篇文章主要介紹了關于ScheduledThreadPoolExecutor不執(zhí)行的原因分析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08

