文档章节

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

小灰灰Blog
 小灰灰Blog
发布于 2018/10/21 19:14
字数 2531
阅读 53
收藏 11

更多Spring文章,欢迎点击 一灰灰Blog-Spring专题

bean的条件注入,除了前面一篇博文中介绍的通过@Conditional注解配合Condition接口的实现之外,还提供了更多简化的注解使用方式,省略了自己实现Condtion接口,本篇博文主要介绍下面几个常用的注解使用方式

  • @ConditionalOnBean
  • @ConditionalOnMissingBean
  • @ConditionalOnClass
  • @ConditionalOnMissingClass
  • @ConditionalOnProperty
  • @ConditionalOnExpression

<!-- more -->

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);
    }
}

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

gif.gif

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

gif.gif

III. 其他

0. 相关

a. 更多博文

基础篇

应用篇

b. 项目源码

1. 一灰灰Blog

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

2. 声明

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

3. 扫描关注

一灰灰blog

QrCode

知识星球

goals

© 著作权归作者所有

共有 人打赏支持
小灰灰Blog
粉丝 188
博文 190
码字总数 334310
作品 0
武汉
程序员
私信 提问
SpringBoot基础篇Bean之条件注入@Condition使用姿势

更多Spring文章,欢迎点击 一灰灰Blog-Spring专题 前面几篇关于Bean的基础博文中,主要集中在Bean的定义和使用,但实际的情况中有没有一些场景是不加载我定义的bean,或者只有满足某些前提条...

小灰灰Blog
2018/10/21
0
0
SpringBoot基础篇之重名Bean的解决与多实例选择

更多Spring文章,欢迎点击 一灰灰Blog-Spring专题 当通过接口的方式注入Bean时,如果有多个子类的bean存在时,具体哪个bean会被注入呢?系统中能否存在两个重名的bean呢?如果可以,那么怎么...

小灰灰Blog
2018/10/22
0
0
SpringBoot自定义条件注解配置

本篇介绍下,如何通过springboot的自定义条件配置,控制Bean的创建 介绍下开发环境 JDK版本1.8 springboot版本是1.5.2 开发工具为 intellij idea(2018.2) 开发环境为 15款MacBook Pro 前言 很多...

贺小五
2018/10/27
0
0
Spring Boot 之Websocket 编程笔记

有时候会用到向前端页推送消息的情况,这时候就会用到WebSocket 编程了, 1.在pom.xml 中添加如下配置 1.1核心是@ServerEndpoint这个注解。这个注解是Javaee标准里的注解,tomcat7以上已经对...

kuchawyz
2018/09/20
0
0
spring boot整合Websocket笔记

特别说明:自学笔记 使用websocket有两种方式: 使用sockjs, 使用h5的标准。 使用Html5标准自然更方便简单,所以记录的是配合h5的使用方法。 1、pom.xml中添加如下: 核心是@ServerEndpoint...

jackcooper2015
2017/12/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

CTS测试流程及注意事项

一、测试前的准备 1.测试环境:在Linux系统下测试 安装SDK,JDK,ADB,Python工具包。 2.下载媒体文件包: 下载网站:https://source.android.com/compatibility/cts/downloads (在网页的最下方...

天王盖地虎626
1分钟前
0
0
技术码霸阐述——Spring Cloud Netflix:熔断器:Hystrix Clients

Netfilix创建了一个名为Hystrix的库,实现了熔断器模式。在微服务架构中,它通常有多个服务调用层。 一: 微服务图 一个底层服务的故障会引发直至用户交互层的连锁故障。在一个设定时长为“m...

java知识分子
7分钟前
0
0
CSS 学习笔记

# 1、CSS介绍 CSS 指层叠样式表 (Cascading Style Sheets),定义如何显示HTML元素。 CSS规则由两个主要的部分构成:选择器,以及一条或多条声明: p{color:red; text-align:left;} 其中p为选择...

davidwbnu
14分钟前
0
0
Git代码防丢指南

我们在日常使用Git的过程中经常会发生一些意外情况,如果处理不当,则可能会出现代码丢失的假象。本文将针对IDEA&Git日常开发中的一些场景,为你层层拨开迷雾,解析常见的错误及其发生原因,...

joymufeng
32分钟前
2
0
传统IDC部署网站(三)

16. chown命令 chown 用来更改一个文件或者目录的所有者护着所属组 -R 级联更改一个目录下所有的目录和文件 chown user1:users 1.txtchown user1.users 1.tx useradd 添加用户的命令 user...

miko0089
43分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部