前言java
瞭解SpringBoot的小夥伴對Conditional註解必定不會陌生,在SpringBoot項目中,Conditional註解被普遍的使用以及擴展出了許多Condition派生註解。雖然Conditional在SpringBoot中被豐富了不少,但它是在Spring Framework 4.0中提出的,因此本文仍是以Spring Framework 爲基礎進行講解。spring
推薦閱讀 黑色的眼睛 の 我的博客 Spring Framework 組件註冊 之 FactoryBean Spring Framework 組件註冊 之 @Import Spring Framework 組件註冊 之 @Componentmarkdown
要使用@Conditional
註解,必須先了解一下Conditiona
接口,它與@Conditional
註解配合使用,經過源碼咱們也能夠看出,使用@Conditional
註解必需要指定實現Conditiona
接口的class。app
@Target({ElementType.TYPE, ElementType.METHOD})
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();
}
複製代碼
在Conditiona
接口中,只定義了一個方法matches
,spring在註冊組件時,也正是根據此方法的返回值TRUE/FALSE
來決定是否將組件註冊到spring容器中學習
@FunctionalInterface
public interface Condition {
/** * Determine if the condition matches. * @param context 條件判斷的上下文環境 * @param metadata 正在檢查的類或方法的註解元數據 */
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
複製代碼
在matches
中咱們能夠獲取到ConditionContext
接口,根據此接口對象能夠獲取BeanDefinitionRegistry
,ConfigurableListableBeanFactory
等重要對象信息,根據這些對象就能夠獲取和檢查spring容器初始化時所包含的全部信息,再結合業務需求,就能夠實現組件註冊時的自定義條件判斷。測試
首先定義兩個普通的JavaBean類spa
@Data
public class Test {
private String id = "@Bean";
}
@Data
public class Test2 {
private String id = "@Conditional";
}
複製代碼
經過配置類和@Bean
註解,向spring容器中註冊組件3d
/** * spring組件配置類 */
@Configuration
public class TestConfiguration {
/** * 向spring容器中註冊Test 類型下beanName爲test的組件 */
@Bean
public Test test() {
return new Test();
}
/** * 根據TestCondition接口的條件判斷向spring容器中註冊Test2組件 */
@Bean
@Conditional(TestCondition.class)
public Test2 test2() {
return new Test2();
}
}
複製代碼
自定義實現Condition
接口code
public class TestCondition implements Condition {
/** * 當IOC容器中包含 Test類的bean定義信息時,條件成立 * * @param context 條件判斷的上下文環境 * @param metadata 正在檢查的類或方法的元數據 * @return 條件是否成立 */
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
return ArrayUtils.isNotEmpty(testBeanNames);
}
}
複製代碼
添加spring容器啓動引導類component
/** * spring 容器啓動引導類,測試 @Conditional 功能 */
@ComponentScan("com.spring.study.ioc.condition")
public class TestConditionalBootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(TestConditionalBootstrap.class);
String[] names = applicationContext.getBeanNamesForType(Test.class);
System.out.println("---Test bean names : " + Arrays.asList(names));
names = applicationContext.getBeanNamesForType(Test2.class);
System.out.println("---Test2 bean names : " + Arrays.asList(names));
applicationContext.close();
}
}
複製代碼
運行spring引導類,控制檯打印結果:
---hasTestBean : true ---Test bean names : [test] ---Test2 bean names : [test2]
由結果能夠看出,Test正常註冊到了spring容器中,知足了TestCondition
接口的條件,全部Test2 也被註冊到了spring容器中,爲了進一步驗證結果,咱們將Test組件刪除掉,僅保留Test2 的註冊,修改配置類以下
/** * spring組件配置類,將Test組件刪除掉 */
@Configuration
public class TestConfiguration {
/** * 根據TestCondition接口的條件判斷向spring容器中註冊Test2組件 */
@Bean
@Conditional(TestCondition.class)
public Test2 test2() {
return new Test2();
}
}
複製代碼
從新運行spring引導類,控制檯打印結果以下:
---hasTestBean : false ---Test bean names : [] ---Test2 bean names : []
因而可知,當Test類在spring容器中沒有註冊時,不知足TestCondition
接口條件,因此Test2 組件也不會被註冊到spring容器中。此時若是將test2()註冊組件上的@Conditional
組件刪除,Test2組件又會被正常註冊到spring容器中。
上面的例子中是將@Conditional
註解添加到了方法上此時條件僅對當前方法生效,@Conditional
註解也能夠加在類
上,此時條件對整個類中的組件註冊均生效。按照上面的案例,作出如下調整:
TestCondition
須要實現ConfigurationCondition
接口,用來對配置類作處理當配置類上添加了@Conditional註解時,須要注意的是,Condition接口中的條件是控制配置類自己仍是控制配置類中的全部組件,所以Spring Framework提供了ConfigurationCondition接口,並使用枚舉值讓咱們自定義選擇。
enum ConfigurationPhase {
/** * Condition接口中的條件控制着配置類自己的註冊,當條件不匹配時,不會添加@configuration類 */
PARSE_CONFIGURATION,
/** * 控制Condition接口中的條件是對配置類中的組件進行解析,不會影響配置類自己的註冊 */
REGISTER_BEAN
}
複製代碼
TestCondition
接口實現修改以下public class TestCondition implements ConfigurationCondition {
/** * 當IOC容器中包含 Test的bean定義信息時,條件成立 * * @param context 條件判斷的上下文環境 * @param metadata 正在檢查的類或方法的元數據 * @return 條件是否成立 */
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
boolean hasTestBean = ArrayUtils.isNotEmpty(testBeanNames);
System.out.println("---hasTestBean : " + hasTestBean);
return hasTestBean;
}
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
}
複製代碼
@Data
@Component // 經過@Component註解直接註冊 Test 組件
public class Test {
private String id = "@Bean";
}
@Data
public class Test2 {
private String id = "Test2: @Conditional";
}
@Data
public class Test3 {
private String id = "Test3: @Conditional";
}
複製代碼
@Conditional
註解/** * spring組件配置類,根據TestCondition接口的條件判斷向spring容器中註冊Test2,Test3組件 */
@Configuration
@Conditional(TestCondition.class)
public class TestConfiguration {
@Bean
public Test3 test3() {
return new Test3();
}
@Bean
public Test2 test2() {
return new Test2();
}
}
複製代碼
Test3
類型的查詢String[] names = applicationContext.getBeanNamesForType(Test3.class);
System.out.println("---Test3 bean names : " + Arrays.asList(names));
複製代碼
啓動引導類,控制檯打印結果以下:
---hasTestBean : true ---Test bean names : [test] ---Test2 bean names : [test2] ---Test3 bean names : [test3]
因而可知,當TestCondition
接口條件匹配時,Test2,Test3均被註冊到spring容器中,若是將Test組件不進行註冊,咱們看看下面的結果。
Test
類上的@Component
註解刪除,其他代碼均不變@Data
public class Test {
private String id = "@Bean";
}
複製代碼
從新啓動引導類,打印結果以下:
---hasTestBean : false ---Test bean names : [] ---Test2 bean names : [] ---Test3 bean names : []
由此能夠看出,TestCondition
的條件控制着配置類中的組件註冊
@Conditional
註解加在方法上時,能夠直接使用Condition
接口進行實現,經過條件匹配,判斷組件是否能夠被註冊到spring容器中@Conditional
註解加在配置類上時,須要使用ConfigurationCondition
接口進行實現,經過ConfigurationPhase
來指定條件匹配對配置類自己註冊的影響。由於Condition
接口的條件是在spring掃描候選組件的過程當中執行的,因此在根據Bean進行條件判斷時,須要注意此問題。若是是自定義的業務需求判斷,不會受此影響。咱們日常在使用spring或者spring MVC時,@Conditional 註解的使用可能並非不少,可是在當下Spring Boot大行其道,而且Spring Boot對@Conditional進行了不少的擴展,因此瞭解@Conditional的使用及原理,也是對Spring Boot的基礎學習作更多的鋪墊。 本文對@Conditional的使用進行了介紹,沒有深刻說明Condition的原理,這些內容將在後續的spring組件掃描過程當中進行說明。
學習永遠都不是一件簡單的事情,能夠有迷茫,能夠懶惰,可是前進的腳步永遠都不能中止。
不積跬步,無以致千里;不積小流,無以成江海;