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或者有更好的建议,欢迎批评指正,不吝感激