天天看點

Springboot Condition 實用講解,隻看一遍包學會

前言

該篇文章,還是一貫的風格,源碼+示例+自言自語的分析,目的隻有一個 :

就是想讓大家都會玩 Condition、Conditional。

正文

先看看 Condition 是被放在包spring context(上下文/容器) 裡面了:

Springboot Condition 實用講解,隻看一遍包學會

spring context(上下文/容器)

接着我們看看作者寫的 Condition 源碼: 

ps: 學東西,一定要看看源碼,往往作者留下的注釋比你自己千方百計找的解釋都好,當然你找到我這邊的文章,另當别論(别當真)。

Springboot Condition 實用講解,隻看一遍包學會

 大緻意思我給各位看官簡述一下:

 利用 Condition ,在一個bean快被注冊前, 我們可以根據任何的自由标準,立即觸發條件的檢查 ,使用 matches方法去 決定 是否注冊。

看完注釋,繼續看下代碼:

Springboot Condition 實用講解,隻看一遍包學會

1.   這是一個 interface,意味着可以實作,然後重寫裡面的方法函數。

2.   可以看到裡面隻有一個mathes(匹對)方法 ,boolean類型傳回值 ,

      意思比較明确易懂,就是匹對 成功了,就是 符合條件;  反之,則反之。  

Springboot Condition 實用講解,隻看一遍包學會

然後看到還有@Conditional注解,這就是配套使用的,我們立刻進入示例實戰,看看怎麼用。

模拟一個場景:

一個實體類Dragon 神龍 ,我們需要建立這個bean,但是有條件。

這個bean被建立的條件是,

通過配置項可以得知目前項目7顆龍珠是否收集完了,隻有收集到7顆才能召喚神龍。

我們先建實體類 Dragon.java :

/**
 * @Author JCccc
 * @Description
 * @Date 2021/08/11 9:55
 */
public class Dragon {

    private String name;
    private Integer age;
    private String master = "JCccc";



    public Dragon(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dragon{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", master='" + master + '\'' +
                '}';
    }
}      

然後在MyBeanConfig.java 裡面把這個bean建立丢到容器裡:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

/**
 * @Author JCccc
 * @Description
 * @Date 2021/10/19 10:01
 */
@Configuration
public class MyBeanConfig {

    @Bean
    @Conditional(DragonCondition.class)
    public Dragon  createDragon() {
        return new Dragon("波倫加",18);
    }


}      

可以看到,我這建立bean,使用了 @Conditional :

@Conditional(DragonCondition.class)

沒錯,這就是我給 這個Dragon實體類 自定義的建立前置條件 ,DragonCondition :

ps: DragonCondition 實作了 Condition接口, 然後重寫了匹對方法mathces,寫了一些判斷邏輯,從配置檔案中取值,看看龍珠顆數是否為7。

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Objects;

/**
 * @Author JCccc
 * @Description 召喚神龍的條件
 * @Date 2021/10/19 10:03
 */
public class DragonCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        if (7 == Integer.parseInt(Objects.requireNonNull(context.getEnvironment().getProperty("summon.dragon-ball")))){
            return  true;
        }

        return  false;
    }
}      

接下來,我們來測試一下,

我們在yml檔案添加配置項:

summon: dragon-ball: 7

然後到測試類裡面簡單寫個測試方法,看看Dragon 這個bean是否能成功被建立 :

import com.elegant.testdemo.happy.Dragon;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;



@SpringBootTest
class TestdemoApplicationTests {

    @Autowired(required = false)
//Autowired 設定為false,允許注入的時候找不到的情況,不會報錯
    private Dragon dragon;
    
    @Test
    void contextLoads() {

        System.out.println(dragon);

    }

}      

執行,可以看到,因為我們條件匹對符合,神龍bean能成功建立:

Springboot Condition 實用講解,隻看一遍包學會

 那麼我們把配置裡面的龍珠顆樹參數改一下,改成 5 :

Springboot Condition 實用講解,隻看一遍包學會

到這裡,其實 基本的Condition 配合 @Conditional 注解的使用已經掌握了。

那麼接下來,繼續,我們開始玩一下這個派生注解:

Springboot Condition 實用講解,隻看一遍包學會

@ConditionalOnBean:當容器中有指定Bean的條件下進行執行個體化。

@ConditionalOnMissingBean:當容器裡沒有指定Bean的條件下進行執行個體化。

@ConditionalOnClass:當classpath類路徑下有指定類的條件下進行執行個體化。

@ConditionalOnMissingClass:當類路徑下沒有指定類的條件下進行執行個體化。

@ConditionalOnWebApplication:當項目是一個Web項目時進行執行個體化。

@ConditionalOnNotWebApplication:當項目不是一個Web項目時進行執行個體化。

@ConditionalOnProperty:當指定的屬性有指定的值時進行執行個體化。

@ConditionalOnExpression:基于SpEL表達式的條件判斷。

@ConditionalOnJava:當JVM版本為指定的版本範圍時觸發執行個體化。

@ConditionalOnResource:當類路徑下有指定的資源時觸發執行個體化。

@ConditionalOnJndi:在JNDI存在的條件下觸發執行個體化。

@ConditionalOnSingleCandidate:當指定的Bean在容器中隻有一個,或者有多個但是指定了首選的Bean時觸發執行個體化。

接下來我挑選幾個常用的細細說說,

@ConditionalOnProperty

也就是說,我們可以讓某個bean建立時,進行 配置值的匹對,符合條件了才能正常建立。

實踐環節

單值匹對

示例:

RoleInitializer 這個bean,如果想讓它正常執行個體化,我們需要檢測yml配置檔案裡面,app.public的值是否為 true :

Springboot Condition 實用講解,隻看一遍包學會

 yml配置檔案我們添加配置值:

Springboot Condition 實用講解,隻看一遍包學會

 啟動項目,可以看到這個bean是符合條件的成功執行個體化:

Springboot Condition 實用講解,隻看一遍包學會

 我們把yml的值改一下:

Springboot Condition 實用講解,隻看一遍包學會

再啟動項目,可以看到這個bean沒有被執行個體化,因為不符合條件:

Springboot Condition 實用講解,隻看一遍包學會

 ps:到這,也許會有一些看官會有想法:

這樣的使用注解去檢測yml裡面的值,跟我們直接拉去配置檔案的值,寫個if判斷 不是一樣嗎?

答案: 看似一樣,實則并不。 寫死的概念。 執行個體化和沒執行個體化的差別。 不多說。

還沒完, 上面是 @ConditionalOnProperty比較簡單的使用例子,單純一個配置值的匹對。

接下來是多值匹對場景

示例 :

RoleInitializer 這個bean,如果想讓它正常執行個體化,我們需要檢測yml配置檔案裡面,app.public的值是否為 true 且 app.vip 的值 也為 true:

Springboot Condition 實用講解,隻看一遍包學會

關系是 且 ,也就是AND ,同時符合的概念,我們可以這麼寫:

@ConditionalOnProperty(name={"app.public","app.vip"}, havingValue="true")
Springboot Condition 實用講解,隻看一遍包學會

@ConditionalOnProperty的注解能夠支撐的、常用的方式也就差不多這些,更多元化的匹對效果是無法支援的,例如我們想要 匹對條件值A 為 1 同時 B 為  2 ,這種情形就已經無法支撐了。

不過我們可以換個概念表達,變成 匹對條件值AB 為 12 .

而且再補充一點,目前@ConditionalOnProperty的使用都是 且 (AND) 關系,如果你要用OR的方式的條件,那麼也是很 sorry,@ConditionalOnProperty 支撐不了。

除非我們使用文章一開始介紹的自定義condition,完全是ok的。

那麼就隻能用自定義condition去實作這些多條件的匹對場景了嗎? 

答案是 : 并不 ,接下來看@ConditionalOnExpression 注解。

@ConditionalOnExpression :基于SpEL表達式的條件判斷。

也是可以用于對配置檔案的屬性做一些匹對條件,但是功能強大很多。

就基于上面說到的例子,我們如果需要匹對的條件是多值:

app.public 值為 true  且 app.vip 值為 high 時 ,才能正常執行個體化RoleInitializer這個bean。

那麼使用@ConditionalOnExpression ,我們可以這麼寫:

@ConditionalOnExpression("'${app.vip}'.equals('high')&&${app.public:true}")      

從這,引申出一些 用法 的介紹:

如果我們想比對的配置項的值 是字元串類型 ,我們寫法是:

@ConditionalOnExpression("'${app.public}'.equals('JC')")

如果是 數字類型 ,我們寫法是:

@ConditionalOnExpression("${app.public}==18")

如果是 布爾類型

@ConditionalOnExpression("${app.public:true}")

那麼還有 與 關系 寫法,使用  && :

@ConditionalOnExpression("'${app.vip}'.equals('high')&&${app.public:true}")

或 關系 寫法,使用  || :

@ConditionalOnExpression("${app.public:true} || ${app.vip:true}")

繼續下一個 ,

@ConditionalOnMissingBean:當容器裡沒有指定Bean的條件下進行執行個體化。

基于文章一開始的 Dargon示例我們繼續 使用一下這個注解,

模拟場景:

 如果  Dargon 神龍沒有符合條件執行個體化成功,那麼我們自動召喚 阿拉丁神燈 作為備用。

Springboot Condition 實用講解,隻看一遍包學會
Springboot Condition 實用講解,隻看一遍包學會

 DragonCondition 自定義的執行個體化條件,回顧一下:

Springboot Condition 實用講解,隻看一遍包學會

 現在故意把yml的神龍召喚配置值改成 不符合條件的數值:

Springboot Condition 實用講解,隻看一遍包學會

寫個測試方法,執行一下:

Springboot Condition 實用講解,隻看一遍包學會

結果跟我們要的是一樣的:

Springboot Condition 實用講解,隻看一遍包學會

我們反之再把相關的配置改成,符合 Dragon bean建立的的條件,這麼一來,MagicLamp肯定就是不用建立出來了: 

Springboot Condition 實用講解,隻看一遍包學會
@ConditionalOnBean:當容器中有指定Bean的條件下進行執行個體化。

繼續閱讀