前面幾篇關於Bean的基礎博文中,主要集中在Bean的定義和使用,但實際的情況中有沒有一些場景是不加載我定義的bean,或者只有滿足某些前提條件的時候才加載我定義的Bean呢?
本篇博文將主要介紹bean的加載中,條件註解@Conditional
的相關使用
<!-- more -->
I. @Conditional
註解
這個註解在Spring4中引入,其主要作用就是判斷條件是否滿足,從而決定是否初始化並向容器註冊Bean
1. 定義
@Conditional
註解定義如下,其內部主要就是利用了Condition接口,來判斷是否滿足條件,從而決定是否需要加載Bean
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
下面是Condtion
接口的定義,這個可以說是最基礎的入口了,其他的所有條件註解,歸根結底,都是通過實現這個接口進行擴展的
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
這個接口中,有個參數比較有意思ConditionContext
,它持有不少有用的對象,可以用來獲取很多系統相關的信息,來豐富條件判斷,接口定義如下
public interface ConditionContext {
// 獲取Bean定義
BeanDefinitionRegistry getRegistry();
// 獲取Bean工程,因此就可以獲取容器中的所有bean
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
// environment 持有所有的配置信息
Environment getEnvironment();
// 資源信息
ResourceLoader getResourceLoader();
// 類加載信息
@Nullable
ClassLoader getClassLoader();
}
2. 使用說明
通過一個小例子,簡單的說一下如何使用Condition和@Conditional
註解,來實現bean的條件加載
首先我們定義一個隨機產生數據的類,其功能就是隨機生成一些數據
public class RandDataComponent<T> {
private Supplier<T> rand;
public RandDataComponent(Supplier<T> rand) {
this.rand = rand;
}
public T rand() {
return rand.get();
}
}
我們目前提供兩種隨機數據生成的bean,但是需要根據配置來選擇具體選中的方式,因此我們如下定義Bean
@Configuration
public class ConditionalAutoConfig {
@Bean
@Conditional(RandIntCondition.class)
public RandDataComponent<Integer> randIntComponent() {
return new RandDataComponent<>(() -> {
Random random = new Random();
return random.nextInt(1024);
});
}
@Bean
@Conditional(RandBooleanCondition.class)
public RandDataComponent<Boolean> randBooleanComponent() {
return new RandDataComponent<>(() -> {
Random random = new Random();
return random.nextBoolean();
});
}
}
上面的配置,先不管@Conditional
註解的內容,單看兩個Bean的定義,一個是定義int隨機數生成;一個是定義boolean隨機生成;
但是我們的系統中,只需要一個隨機數據生成器即可,我們選擇根據配置conditional.rand.type
的值來選擇到底用哪個,配置如下
# int 表示選擇隨機產生int數據; 非int 表示隨機產生boolean數據
conditional.rand.type=int
接下來就得看這個條件如何加上了,也就是上面配置類ConditionalAutoConfig
中兩個註解的內容了,兩個類都是實現Condition
的接口,具體如下
public class RandBooleanCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
return "boolean".equalsIgnoreCase(type);
}
}
public class RandIntCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
return "int".equalsIgnoreCase(type);
}
}
上面的實現也比較清晰,獲取配置值,然後判斷,並返回true/fase;返回true,則表示這個條件滿足,那麼這個Bean就可以被加載了;否則這個Bean就不會創建
3. 測試與驗證
針對上面的配置與實現,寫一個測試類如下
@RestController
@RequestMapping(path = "/conditional")
public class ConditionalRest {
@Autowired
private RandDataComponent randDataComponent;
@GetMapping(path = "/show")
public String show() {
String type = environment.getProperty("conditional.rand.type");
return randDataComponent.rand() + " >>> " + type;
}
}
當配置文件的值爲int時,每次訪問返回的應該都是正整數,演示如下圖
將配置的值改成boolean之後,再次測試如下圖
II. 擴展與小結
上面的測試演示了通過配置文件選擇注入Bean的情況,如果一個Bean是通過自動掃描加載的,是否可以直接在Bean的類上添加註解來決定是否載入呢?
1. 自動掃描Bean的條件加載
從使用來講,和前面的沒有什麼區別,只是將註解放在具體的類上而言,同樣給出一個示例,先定義一個bean
@Component
@Conditional(ScanDemoCondition.class)
public class ScanDemoBean {
@Value("${conditional.demo.load}")
private boolean load;
public boolean getLoad() {
return load;
}
}
對應的判斷條件如下,當配置文件中conditional.demo.load
爲true時,纔會加載這個配置,否則不實例化
public class ScanDemoCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return "true".equalsIgnoreCase(conditionContext.getEnvironment().getProperty("conditional.demo.load"));
}
}
測試類和前面差不多,稍微注意下的就是自動注入時,改一下必要條件,避免bean不存在時報錯
@Autowired(required = false)
private ScanDemoBean scanDemoBean;
@GetMapping(path = "/scan")
public String showDemo() {
String type = environment.getProperty("conditional.demo.load");
if (scanDemoBean == null) {
return "not exists! >>>" + type;
} else {
return "load : " + scanDemoBean.getLoad() + " >>>" + type;
}
}
當配置爲true時,bean應該存在,走上面的else邏輯
當配置爲false時,不會加載bean,走if邏輯
2. 小結
通過@Conditional
註解配合Condition
接口,來決定給一個bean是否創建和註冊到Spring容器中,從而實現有選擇的加載bean
a. 優勢
這樣做的目的是什麼呢?
- 當有多個同名bean時,怎麼抉擇的問題
- 解決某些bean的創建有其他依賴條件的case
b. 更多註解
上面可以控制bean的創建,但通過上面的流程,會發現有一點繁瑣,有沒有什麼方式可以簡化上面的流程呢?
只用一個註解就好,不要自己再來實現Condtion接口,Spring框架提供了一系列相關的註解,如下表
註解 | 說明 |
---|---|
@ConditionalOnSingleCandidate |
當給定類型的bean存在並且指定爲Primary的給定類型存在時,返回true |
@ConditionalOnMissingBean |
當給定的類型、類名、註解、暱稱在beanFactory中不存在時返回true.各類型間是or的關係 |
@ConditionalOnBean |
與上面相反,要求bean存在 |
@ConditionalOnMissingClass |
當給定的類名在類路徑上不存在時返回true,各類型間是and的關係 |
@ConditionalOnClass |
與上面相反,要求類存在 |
@ConditionalOnCloudPlatform |
當所配置的CloudPlatform爲激活時返回true |
@ConditionalOnExpression |
spel表達式執行爲true |
@ConditionalOnJava |
運行時的java版本號是否包含給定的版本號.如果包含,返回匹配,否則,返回不匹配 |
@ConditionalOnProperty |
要求配置屬性匹配條件 |
@ConditionalOnJndi |
給定的jndi的Location 必須存在一個.否則,返回不匹配 |
@ConditionalOnNotWebApplication |
web環境不存在時 |
@ConditionalOnWebApplication |
web環境存在時 |
@ConditionalOnResource |
要求制定的資源存在 |
III. 其他
0. 相關
a. 更多博文
基礎篇
- 181009-SpringBoot基礎篇Bean之基本定義與使用
- 181012-SpringBoot基礎篇Bean之自動加載
- 181013-SpringBoot基礎篇Bean之動態註冊
- 181018-SpringBoot基礎篇Bean之條件注入@Condition使用姿勢
- 181019-SpringBoot基礎篇Bean之@ConditionalOnBean與@ConditionalOnClass
- 181019-SpringBoot基礎篇Bean之條件注入@ConditionalOnProperty
- 181019-SpringBoot基礎篇Bean之條件注入@ConditionalOnExpression
應用篇