SpringBoot基础篇Bean之条件注入之注解使用

栏目: Java · 发布时间: 6年前

内容简介:bean的条件注入,除了前面一篇博文中介绍的通过当Bean不存在时,创建一个默认的Bean,在Spring的生态中可以说比较常见了;接下来看下这种方式可以怎么用要求bean存在时,才会创建这个bean;如我提供了一个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);
    }
}
复制代码

根据前面的分析,返回的结果应该是三个存在,一个不存在;下图执行和我们预期一致

SpringBoot基础篇Bean之条件注入之注解使用

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);
    }
}
复制代码

执行后结果如下,一如预期

SpringBoot基础篇Bean之条件注入之注解使用

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,运行结果如下

SpringBoot基础篇Bean之条件注入之注解使用

III. 其他

0. 相关

a. 更多博文

基础篇

应用篇

b. 项目源码

1. 一灰灰Blog

  • 一灰灰Blog个人博客blog.hhui.top
  • 一灰灰Blog-Spring专题博客spring.hhui.top

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Parsing Techniques

Parsing Techniques

Dick Grune、Ceriel J.H. Jacobs / Springer / 2010-2-12 / USD 109.00

This second edition of Grune and Jacobs' brilliant work presents new developments and discoveries that have been made in the field. Parsing, also referred to as syntax analysis, has been and continues......一起来看看 《Parsing Techniques》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具