SpringBoot基礎篇Bean之條件注入之註解使用
bean的條件注入,除了前面一篇博文中介紹的通過 @Conditional
註解配合 Condition
介面的實現之外,還提供了更多簡化的註解使用方式,省略了自己實現 Condtion
介面,本篇博文主要介紹下面幾個常用的註解使用方式
@ConditionalOnBean @ConditionalOnMissingBean @ConditionalOnClass @ConditionalOnMissingClass @ConditionalOnProperty @ConditionalOnExpression
I. Bean的存在與否作為條件
當Bean不存在時,建立一個預設的Bean,在Spring的生態中可以說比較常見了;接下來看下這種方式可以怎麼用
1. @ConditionalOnBean
要求bean存在時,才會建立這個bean;如我提供了一個bean名為 RedisOperBean
,用於封裝redis相關的操作;但是我這個bean需要依賴 restTemplate
這個bean,只有當應用引入了redis的相關依賴,並存在 RestTemplate
這個bean的時候,我這個bean才會生效
假設bean的定義如下
@Component @ConditionalOnBean(name="redisTemplate") public class RedisOperBean { private final RedisTemplate redisTemplate; public RedisOperBean(RedisTemplate redisTemplate) { // ... } } 複製程式碼
這樣的好處就是我提供的這個第三方包,如果被使用者A間接依賴(但是A本身不需要操作redis),也不會因為建立 RedisOperBean
而拋異常
產生異常的原因是因為找不到RestTemplate的bean,因此無法例項化RedisOperBean,從而丟擲異常
a. 註解定義
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { // bean型別 Class<?>[] value() default {}; // bean型別 String[] type() default {}; // 要求bean上擁有指定的註解 Class<? extends Annotation>[] annotation() default {}; // bean names String[] name() default {}; SearchStrategy search() default SearchStrategy.ALL; } 複製程式碼
b. 測試用例
構建一個簡單的測試用例,先定義一個基礎的bean
public class DependedBean { } 複製程式碼
再定義一個依賴只有上面的bean存在時,才會載入的bean
public class LoadIfBeanExist { private String name; public LoadIfBeanExist(String name) { this.name = name; } public String getName() { return "load if bean exists: " + name; } } 複製程式碼
接下來就是bean的定義了
@Bean public DependedBean dependedBean() { return new DependedBean(); } /** * 只有當DependedBean 存在時,才會建立bean: `LoadIfBeanExist` * * @return */ @Bean @ConditionalOnBean(name = "dependedBean") public LoadIfBeanExist loadIfBeanExist() { return new LoadIfBeanExist("dependedBean"); } 複製程式碼
根據上面的測試用例, LoadIfBeanExist
是會被正常載入的; 具體結果看後面的例項演示
2. ConditionalOnMissingBean
和前面一個作用正好相反的,上面是要求存在bean,而這個是要求不存在
a. 介面定義
public @interface ConditionalOnMissingBean { Class<?>[] value() default {}; String[] type() default {}; /** * The class type of beans that should be ignored when identifying matching beans. */ Class<?>[] ignored() default {}; /** * The class type names of beans that should be ignored when identifying matching * beans. */ String[] ignoredType() default {}; Class<? extends Annotation>[] annotation() default {}; String[] name() default {}; SearchStrategy search() default SearchStrategy.ALL; } 複製程式碼
b. 測試用例
同樣定義一個bean不存在時,才建立的bean
public class LoadIfBeanNotExists { public String name; public LoadIfBeanNotExists(String name) { this.name = name; } public String getName() { return "load if bean not exists: " + name; } } 複製程式碼
對應的bean配置如下
/** * 只有當沒有notExistsBean時,才會建立bean: `LoadIfBeanNotExists` * * @return */ @Bean @ConditionalOnMissingBean(name = "notExistsBean") public LoadIfBeanNotExists loadIfBeanNotExists() { return new LoadIfBeanNotExists("notExistsBean"); } 複製程式碼
因為沒有notExistsBean,所以上面這個bean也應該被正常註冊
3. 例項演示
因為bean的是否存在和class的是否存在有較大的相似性,因此例項演示放在下一小節,一起測試
II. Class的存在與否作為條件
從使用來看,和前面基本上沒有太大的區別,無非就是將bean換成了class;這樣就可以避免因為 Class Not Found
導致的編譯異常了
1. @ConditionalOnClass
要求class存在
a. 註解定義
public @interface ConditionalOnClass { Class<?>[] value() default {}; /** * The classes names that must be present. * @return the class names that must be present. */ String[] name() default {}; } 複製程式碼
b. 測試用例
先定義一個class
public class DependedClz { } 複製程式碼
然後依賴class存在的bean
public class LoadIfClzExists { private String name; public LoadIfClzExists(String name) { this.name = name; } public String getName() { return "load if exists clz: " + name; } } 複製程式碼
接下來就是Bean的配置
/** * 當引用了 {@link DependedClz} 類之後,才會建立bean: `LoadIfClzExists` * * @return */ @Bean @ConditionalOnClass(DependedClz.class) public LoadIfClzExists loadIfClzExists() { return new LoadIfClzExists("dependedClz"); } 複製程式碼
因為類存在,所以測試時,這個bean應該被正常註冊
2. @ConditionalOnMissingClass
class不存在時,才會載入bean
a. 註解定義
public @interface ConditionalOnMissingClass { String[] value() default {}; } 複製程式碼
b. 測試用例
定義一個class缺少時才會建立的bean
public class LoadIfClzNotExists { private String name; public LoadIfClzNotExists(String name) { this.name = name; } public String getName() { return "load if not exists clz: " + name; } } 複製程式碼
bean的配置如下
/** * 當系統中沒有 com.git.hui.boot.conditionbean.example.depends.clz.DependedClz類時,才會建立這個bean * * @return */ @Bean @ConditionalOnMissingClass("com.git.hui.boot.conditionbean.example.depends.clz.DependedClz") public LoadIfClzNotExists loadIfClzNotExists() { return new LoadIfClzNotExists("com.git.hui.boot.conditionbean.example.depends.clz.DependedClz"); } 複製程式碼
因為上面這個類存在,所以這個bean不應該被正常註冊
3. 例項演示
起一個rest服務,測試下上面的四個bean是否正常
@RestController @RequestMapping("depends") public class DependRest { @Autowired private LoadIfBeanExist loadIfBeanExist; @Autowired private LoadIfBeanNotExists loadIfBeanNotExists; @Autowired private LoadIfClzExists loadIfClzExists; @Autowired(required = false) private LoadIfClzNotExists loadIfClzNotExists; @GetMapping(path = "show") public String show() { Map<String, String> result = new HashMap<>(4); // 存在 result.put("loadIfBeanExist", loadIfBeanExist == null ? "null ==> false!" : loadIfBeanExist.getName()); // 存在 result.put("loadIfBeanNotExists", loadIfBeanNotExists == null ? "null ==> false!" : loadIfBeanNotExists.getName()); // 存在 result.put("loadIfClzExists", loadIfClzExists == null ? "null ==> false!" : loadIfClzExists.getName()); // 不存在 result.put("loadIfClzNotExists", loadIfClzNotExists == null ? "null ==> true!" : loadIfClzNotExists.getName()); return JSONObject.toJSONString(result); } } 複製程式碼
根據前面的分析,返回的結果應該是三個存在,一個不存在;下圖執行和我們預期一致

III. 配置屬性作為條件
主要是根據配置引數,來決定是否需要建立這個bean,這樣就給了我們一個根據配置來控制Bean的選擇的手段了,如前面一篇博文中根據配置來選擇是隨機生成boolean還是隨機生成int;只需要更改配置即可
1. @ConditionalOnProperty
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { /** * Alias for {@link #name()}. * @return the names */ String[] value() default {}; // 配置字首 String prefix() default ""; // 配置名 String[] name() default {}; // 要求配置存在,且包含某個值 String havingValue() default ""; // 即便沒有配置,也依然建立 boolean matchIfMissing() default false; } 複製程式碼
2. 例項測試
a. 測試用例
測試幾個常用的姿勢,一是根據配置是否存在,來決定是否建立
public class PropertyExistBean { private String name; public PropertyExistBean(String name) { this.name = name; } public String getName() { return "property : " + name; } } public class PropertyNotExistBean { private String name; public PropertyNotExistBean(String name) { this.name = name; } public String getName() { return "no property" + name; } } 複製程式碼
對應的bean配置如下
/** * 配置存在時才會載入這個bean * * @return */ @Bean @ConditionalOnProperty("conditional.property") public PropertyExistBean propertyExistBean() { return new PropertyExistBean(environment.getProperty("conditional.property")); } /** * 即便配置不存在時,也可以載入這個bean * * @return */ @Bean @ConditionalOnProperty(name = "conditional.property.no", matchIfMissing = true) public PropertyNotExistBean propertyNotExistBean() { return new PropertyNotExistBean("conditional.property"); } 複製程式碼
當配置存在,且value匹配時
public class PropertyValueExistBean { public String name; public PropertyValueExistBean(String name) { this.name = name; } public String getName() { return "property value exist: " + name; } } public class PropertyValueNotExistBean { public String name; public PropertyValueNotExistBean(String name) { this.name = name; } public String getName() { return "property value not exist: " + name; } } 複製程式碼
對應的配置如下
@Bean @ConditionalOnProperty(name = {"conditional.property"}, havingValue = "properExists") public PropertyValueExistBean propertyValueExistBean() { return new PropertyValueExistBean("properExists"); } @Bean @ConditionalOnProperty(name = {"conditional.property"}, havingValue = "properNotExists") public PropertyValueNotExistBean propertyValueNotExistBean() { return new PropertyValueNotExistBean("properNotExists"); } 複製程式碼
接下來就是配置的引數
conditional.property=properExists 複製程式碼
b. 例項演示
根據前面的分析,上面的四個bean中, PropertyExistBean
, PropertyNotExistBean
, PropertyValueExistBean
應該存在;而 PropertyValueNotExistBean
因為配置值不匹配,不會建立
測試程式碼如下
@RestController @RequestMapping(path = "property") public class PropertyRest { @Autowired(required = false) private PropertyExistBean propertyExistBean; @Autowired(required = false) private PropertyNotExistBean propertyNotExistBean; @Autowired(required = false) private PropertyValueExistBean propertyValueExistBean; @Autowired(required = false) private PropertyValueNotExistBean propertyValueNotExistBean; @GetMapping(path = "show") public String show() { Map<String, String> result = new HashMap<>(4); // 存在 result.put("propertyExistBean", propertyExistBean == null ? "null ===> false" : propertyExistBean.getName()); // 存在 result.put("propertyNotExistBean", propertyNotExistBean == null ? "null ===> false" : propertyNotExistBean.getName()); // 存在 result.put("propertyValueExistBean", propertyValueExistBean == null ? "null ==> false" : propertyValueExistBean.getName()); // 不存在 result.put("propertyValueNotExistBean", propertyValueNotExistBean == null ? "null ==> true" : propertyValueNotExistBean.getName()); return JSONObject.toJSONString(result); } } 複製程式碼
執行後結果如下,一如預期

IV. 表示式方式
相比較前面的Bean,Class是否存在,配置引數是否存在或者有某個值而言,這個依賴SPEL表示式的,就顯得更加的高階了;其主要就是執行Spel表示式,根據返回的true/false來判斷是否滿足條件
至於SPEL是什麼東西,後面會有專文進行解釋,此處不加以展開。下面以一個簡單的demo進行演示它的使用姿勢
1. @ConditionalOnExpression
介面定義
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnExpressionCondition.class) public @interface ConditionalOnExpression { /** * The SpEL expression to evaluate. Expression should return {@code true} if the * condition passes or {@code false} if it fails. * @return the SpEL expression */ String value() default "true"; } 複製程式碼
2. 例項測試
用一個簡單的例子,當配置引數中,根據是否滿足某個條件來決定是否需要載入bean
a. 測試用例
定義一個滿足條件和一個不滿足的bean
public class ExpressFalseBean { private String name; public ExpressFalseBean(String name) { this.name = name; } public String getName() { return "express bean :" + name; } } public class ExpressTrueBean { private String name; public ExpressTrueBean(String name) { this.name = name; } public String getName() { return "express bean :" + name; } } 複製程式碼
重點關注下bean的配置
@Configuration public class ExpressAutoConfig { /** * 當存在配置,且配置為true時才建立這個bean * @return */ @Bean @ConditionalOnExpression("#{'true'.equals(environment['conditional.express'])}") public ExpressTrueBean expressTrueBean() { return new ExpressTrueBean("express true"); } /** * 配置不存在,或配置的值不是true時,才建立bean * @return */ @Bean @ConditionalOnExpression("#{!'true'.equals(environment.getProperty('conditional.express'))}") public ExpressFalseBean expressFalseBean() { return new ExpressFalseBean("express != true"); } } 複製程式碼
對應的配置如下
conditional.express=true 複製程式碼
b. 例項演示
@RestController @RequestMapping(path = "express") public class ExpressRest { @Autowired(required = false) private ExpressTrueBean expressTrueBean; @Autowired(required = false) private ExpressFalseBean expressFalseBean; @GetMapping(path = "show") public String show() { Map<String, String> result = new HashMap<>(4); result.put("expressTrueBean", expressTrueBean == null ? "null ==> false" : expressTrueBean.getName()); result.put("expressFalseBean", expressFalseBean == null ? "null ==> true": expressFalseBean.getName()); return JSONObject.toJSONString(result); } } 複製程式碼
上面的執行, expressTrueBean
應該存在,另外一個為null,執行結果如下

III. 其他
0. 相關
a. 更多博文
基礎篇
- ofollow,noindex">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
應用篇
b. 專案原始碼
- 工程: spring-boot-demo
- module: 007-conditionbean
1. 一灰灰Blog
- 一灰灰Blog個人部落格blog.hhui.top
- 一灰灰Blog-Spring專題部落格spring.hhui.top
一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 宣告
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激