更多Spring文章,欢迎点击 一灰灰Blog-Spring专题
bean的条件注入,除了前面一篇博文中介绍的通过@Conditional
注解配合Condition
接口的实现之外,还提供了更多简化的注解使用方式,省略了自己实现Condtion
接口,本篇博文主要介绍下面几个常用的注解使用方式
@ConditionalOnBean
@ConditionalOnMissingBean
@ConditionalOnClass
@ConditionalOnMissingClass
@ConditionalOnProperty
@ConditionalOnExpression
当Bean不存在时,创建一个默认的Bean,在Spring的生态中可以说比较常见了;接下来看下这种方式可以怎么用
@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,从而抛出异常
@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;
}
构建一个简单的测试用例,先定义一个基础的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
是会被正常加载的; 具体结果看后面的实例演示
ConditionalOnMissingBean
和前面一个作用正好相反的,上面是要求存在bean,而这个是要求不存在
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;
}
同样定义一个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也应该被正常注册
因为bean的是否存在和class的是否存在有较大的相似性,因此实例演示放在下一小节,一起测试
从使用来看,和前面基本上没有太大的区别,无非就是将bean换成了class;这样就可以避免因为Class Not Found
导致的编译异常了
@ConditionalOnClass
要求class存在
public @interface ConditionalOnClass {
Class<?>[] value() default {};
/**
* The classes names that must be present.
* @return the class names that must be present.
*/
String[] name() default {};
}
先定义一个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应该被正常注册
@ConditionalOnMissingClass
class不存在时,才会加载bean
public @interface ConditionalOnMissingClass {
String[] value() default {};
}
定义一个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不应该被正常注册
起一个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);
}
}
根据前面的分析,返回的结果应该是三个存在,一个不存在;下图执行和我们预期一致
主要是根据配置参数,来决定是否需要创建这个bean,这样就给了我们一个根据配置来控制Bean的选择的手段了,如前面一篇博文中根据配置来选择是随机生成boolean还是随机生成int;只需要更改配置即可
@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;
}
测试几个常用的姿势,一是根据配置是否存在,来决定是否创建
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
根据前面的分析,上面的四个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);
}
}
执行后结果如下,一如预期
相比较前面的Bean,Class是否存在,配置参数是否存在或者有某个值而言,这个依赖SPEL表达式的,就显得更加的高级了;其主要就是执行Spel表达式,根据返回的true/false来判断是否满足条件
至于SPEL是什么东西,后面会有专文进行解释,此处不加以展开。下面以一个简单的demo进行演示它的使用姿势
@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";
}
用一个简单的例子,当配置参数中,根据是否满足某个条件来决定是否需要加载bean
定义一个满足条件和一个不满足的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
@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,运行结果如下
基础篇
应用篇
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
一灰灰blog
知识星球