前言
該篇文章,還是一貫的風格,源碼+示例+自言自語的分析,目的隻有一個 :
就是想讓大家都會玩 Condition、Conditional。
正文
先看看 Condition 是被放在包spring context(上下文/容器) 裡面了:
spring context(上下文/容器)
接着我們看看作者寫的 Condition 源碼:
ps: 學東西,一定要看看源碼,往往作者留下的注釋比你自己千方百計找的解釋都好,當然你找到我這邊的文章,另當别論(别當真)。
大緻意思我給各位看官簡述一下:
利用 Condition ,在一個bean快被注冊前, 我們可以根據任何的自由标準,立即觸發條件的檢查 ,使用 matches方法去 決定 是否注冊。
看完注釋,繼續看下代碼:
1. 這是一個 interface,意味着可以實作,然後重寫裡面的方法函數。
2. 可以看到裡面隻有一個mathes(匹對)方法 ,boolean類型傳回值 ,
意思比較明确易懂,就是匹對 成功了,就是 符合條件; 反之,則反之。
然後看到還有@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能成功建立:
那麼我們把配置裡面的龍珠顆樹參數改一下,改成 5 :
到這裡,其實 基本的Condition 配合 @Conditional 注解的使用已經掌握了。
那麼接下來,繼續,我們開始玩一下這個派生注解:
@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 :
yml配置檔案我們添加配置值:
啟動項目,可以看到這個bean是符合條件的成功執行個體化:
我們把yml的值改一下:
再啟動項目,可以看到這個bean沒有被執行個體化,因為不符合條件:
ps:到這,也許會有一些看官會有想法:
這樣的使用注解去檢測yml裡面的值,跟我們直接拉去配置檔案的值,寫個if判斷 不是一樣嗎?
答案: 看似一樣,實則并不。 寫死的概念。 執行個體化和沒執行個體化的差別。 不多說。
還沒完, 上面是 @ConditionalOnProperty比較簡單的使用例子,單純一個配置值的匹對。
接下來是多值匹對場景
示例 :
RoleInitializer 這個bean,如果想讓它正常執行個體化,我們需要檢測yml配置檔案裡面,app.public的值是否為 true 且 app.vip 的值 也為 true:
關系是 且 ,也就是AND ,同時符合的概念,我們可以這麼寫:
@ConditionalOnProperty(name={"app.public","app.vip"}, havingValue="true")
@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 神龍沒有符合條件執行個體化成功,那麼我們自動召喚 阿拉丁神燈 作為備用。
DragonCondition 自定義的執行個體化條件,回顧一下:
現在故意把yml的神龍召喚配置值改成 不符合條件的數值:
寫個測試方法,執行一下:
結果跟我們要的是一樣的:
我們反之再把相關的配置改成,符合 Dragon bean建立的的條件,這麼一來,MagicLamp肯定就是不用建立出來了:
@ConditionalOnBean:當容器中有指定Bean的條件下進行執行個體化。