一文深入理解ConfigurationConditon 介面

Hi丶ImViper發表於2020-10-22

一文了解ConfigurationConditon 介面

ConfigurationCondition 介面說明

@Conditional 和 Condition

​ 在瞭解ConfigurationCondition 介面之前,先通過一個示例來了解一下@Conditional 和 Condition。

  • 首先新建一個Maven專案(可以使用SpringBoot快速搭建),新增Spring4.0 的pom.xml 依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cxuan.configuration</groupId>
    <artifactId>configuration-condition</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>configuration-condition</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring.version>4.3.13.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • 新建一個IfBeanAExistsCondition 類,該類繼承了Condition介面,提供某些註冊條件的邏輯
public class IfBeanAExistsCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        boolean IfContainsbeanA = context.getBeanFactory().containsBeanDefinition("beanA");
        return IfContainsbeanA;
    }
}

Condition是一個介面,裡面只有一個方法就是matches,上述表明如果ConditionContext的beanFactory包括名稱為beanA的bean就返回true,否則返回false不進行註冊。

  • 為了測試Condition是否可用,我們新建一個ConfigurationConditionApplication類,註冊兩個Bean分別為BeanA和BeanB,BeanB的註冊條件是BeanA首先進行註冊,採用手動註冊和重新整理的方式。詳見https://www.cnblogs.com/cxuanBlog/p/10958307.html,具體程式碼如下:
public class ConfigurationConditionApplication {

    private static void loadContextAndVerifyBeans(Class...classToRegistry){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(classToRegistry);
        context.refresh();
        System.out.println("Has BeanA? " + context.containsBean("beanA"));
        System.out.println("Has BeanB? " + context.containsBean("beanB"));
    }


    public static void main(String[] args) {
        loadContextAndVerifyBeans(BeanA.class);
        loadContextAndVerifyBeans(BeanA.class,BeanB.class);
        loadContextAndVerifyBeans(BeanB.class);
        loadContextAndVerifyBeans(BeanB.class,BeanA.class);
    }
}

@Configuration()
class BeanA{}

@Conditional(IfBeanAExistsCondition.class)
@Configuration()
class BeanB{}

輸出結果:

...
Has BeanA? true
Has BeanB? false
...
Has BeanA? true
Has BeanB? true
...
Has BeanA? false
Has BeanB? false
...
Has BeanA? true
Has BeanB? false

來解釋一下上面的輸出結果,第一次只註冊了一個BeanA的bean,@Configuration標註的BeanA預設註冊的definitionName為beanA,首字母小寫。

第二次同時傳入了BeanA.class 和 BeanB.class, 由於BeanB的註解上標明@Conditional(IfBeanAExistsCondition.class)表示的是註冊BeanA之後才會註冊BeanB,所以註冊了beanA,因為beanA被註冊了,所以同時也就註冊了beanB。

第三次只傳入了BeanB.class,因為沒有註冊BeanA和BeanB,所以兩次輸出都是false。

第四次先傳入了BeanB.class,後又傳入了BeanA.class,根據載入順序來看,BeanB.class 首先被載入,然後是BeanA.class 被載入,BeanB被載入的時候BeanA.class 還沒有被注入,之後BeanA才會注入,所以輸出的結果是true和false。

上述例子可以把BeanA和BeanB類放入ConfigurationConditionApplication中,類似

public class ConfigurationConditionApplication {
	 
@Configuration()
static class BeanA{}

@Conditional(IfBeanAExistsCondition.class)
@Configuration()
static class BeanB{}
 
}

但是需要把BeanA和BeanB定義為靜態類,因為靜態類與外部類無關能夠獨立存在,如果定義為非靜態的,啟動會報錯。

關於ConfigurationConditon

​ ConfigurationCondition介面是Spring4.0提供的註解。位於org.springframework.context.annotation包內,繼承於Condition介面。Condition介面和@Configuration以及@Conditional介面為bean的註冊提供更細粒度的控制,允許某些Condition在匹配時根據配置階段進行調整。

public interface ConfigurationCondition extends Condition {

	// 評估condition返回的ConfigurationPhase
	ConfigurationPhase getConfigurationPhase();

	// 可以評估condition的各種配置階段。
	enum ConfigurationPhase {
    
		// @Condition 應該被評估為正在解析@Configuration類
		// 如果此時條件不匹配,則不會新增@Configuration 類。
		PARSE_CONFIGURATION,

		// 新增常規(非@Configuration)bean時,應評估@Condition。Condition 將不會阻止@Configuration 類
		// 新增。在評估條件時,將解析所有@Configuration
		REGISTER_BEAN
	}

}

getConfigurationPhase()方法返回ConfigurationPhase 的列舉。列舉類內定義了兩個enum,PARSE_CONFIGURATION 和 REGISTER_BEAN,表示不同的註冊階段。

​ 我們現在對condition實現更細粒度的控制,實現了ConfigurationCondition介面,我們現在需要實現getConfigurationPhase()方法獲得condition需要評估的階段。

  • 新建IfBeanAExistsConfigurationCondition類,實現了ConfigurationCondition介面,分別返回ConfigurationPhase.REGISTER_BEAN 和 ConfigurationPhase.PARSE_CONFIGURATION 階段。
public class IfBeanAExistsConfigurationCondition implements ConfigurationCondition {

    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;
    }

//    @Override
//    public ConfigurationPhase getConfigurationPhase() {
//        return ConfigurationPhase.PARSE_CONFIGURATION;
//    }

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getBeanFactory().containsBeanDefinition("beanA");
    }
}
  • 新建SpringConfigurationConditionExample類,與上述測試類基本相同,就是把@Conditional 換為了**@Conditional(IfBeanAExistsConfigurationCondition.class)**

測試類啟動,輸出結果

...
Has BeanA? true
Has BeanB? false
...
Has BeanA? true
Has BeanB? true
...
Has BeanA? false
Has BeanB? false
...
Has BeanA? true
Has BeanB? true

也就是說,如果返回的是PARSE_CONFIGURATION階段的話,不會阻止@Configuration的標記類的註冊順序,啥意思呢?

第一個結果,只註冊了BeanA,因為只有BeanA載入。

第二個結果,註冊了BeanA和BeanB,因為BeanA和BeanB都被載入

第三個結果,因為BeanB註冊的條件是BeanA註冊,因為BeanA沒有註冊,所以BeanB不會註冊

第四個結果,不論BeanA和BeanB的載入順序如何,都會直接進行註冊。

  • 如果把REGISTER_BEAN改為**PARSE_CONFIGURATION **,會發現載入順序第一次一致。