Spring Framework 條件裝配 之 @Conditional

Spring Framework 條件裝配 之 @Conditional

前言

瞭解SpringBoot的小夥伴對Conditional註解一定不會陌生,在SpringBoot項目中,Conditional註解被廣泛的使用以及擴展出了許多Condition派生註解。雖然Conditional在SpringBoot中被豐富了很多,但它是在Spring Framework 4.0中提出的,所以本文還是以Spring Framework 爲基礎進行講解。

推薦閱讀
黑色的眼睛 の 個人博客
Spring Framework 組件註冊 之 FactoryBean
Spring Framework 組件註冊 之 @Import
Spring Framework 組件註冊 之 @Component

@Conditional 說明

要使用@Conditional註解,必須先了解一下Conditiona接口,它與@Conditional註解配合使用,通過源碼我們也可以看出,使用@Conditional註解必須要指定實現Conditiona接口的class。

@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接口,根據此接口對象可以獲取BeanDefinitionRegistryConfigurableListableBeanFactory等重要對象信息,根據這些對象就可以獲取和檢查spring容器初始化時所包含的所有信息,再結合業務需求,就可以實現組件註冊時的自定義條件判斷。

@Conditional 使用

首先定義兩個普通的JavaBean類

@Data
public class Test {
    private String id = "@Bean";
}

@Data
public class Test2 {
    private String id = "@Conditional";
}

通過配置類和@Bean註解,向spring容器中註冊組件

/**
 * 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接口

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容器啓動引導類

/**
 * 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;
    }
}
  • 對javaBean類和配置類進行調整
@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 的注意點

  • @Conditional註解加在方法上時,可以直接使用Condition接口進行實現,通過條件匹配,判斷組件是否可以被註冊到spring容器中
  • @Conditional註解加在配置類上時,需要使用ConfigurationCondition接口進行實現,通過ConfigurationPhase來指定條件匹配對配置類本身註冊的影響。因爲Condition接口的條件是在spring掃描候選組件的過程中執行的,所以在根據Bean進行條件判斷時,需要注意此問題。如果是自定義的業務需求判斷,不會受此影響。

總結

我們平常在使用spring或者spring MVC時,@Conditional 註解的使用可能並不是很多,但是在當下Spring Boot大行其道,並且Spring Boot對@Conditional進行了很多的擴展,所以瞭解@Conditional的使用及原理,也是對Spring Boot的基礎學習做更多的鋪墊。
本文對@Conditional的使用進行了介紹,沒有深入說明Condition的原理,這些內容將在後續的spring組件掃描過程中進行說明。

學習永遠都不是一件簡單的事情,可以有迷茫,可以懶惰,但是前進的腳步永遠都不能停止。

不積跬步,無以至千里;不積小流,無以成江海;

黑色的眼睛 の 個人博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章