Spring生态研习【五】:Springboot中bean的条件注入
在springboot中,开发的确变的简单了很多,但是,开发者现在希望开发傻瓜式的方便搞定项目中的各种奇怪的需求最好了,不用烧脑,本来程序猿的生活就是枯燥的,不要再给自己添加更多的烦恼。
今天,就为了方便这点,介绍下,如何解决在开发过程中,一些场景下,为了实现一个配置模块中,基于开关量或者选择配置项,实现不同功能,例如,在一个session共享模块当中,解决session是基于header传递还是基于cookie传递这两种应用场景,有些应用中希望基于header传递sessionId,但是有些应用中希望基于cookie传递sessionId,然后,session共享模块,是一个非常基础的组件,差不多是一个开箱即用的功能块。所以呢,最好能配置好,然后只需要基于配置文件中的某个选项,就能实现运行在不同的工作模式下。这个能否做到呢?真的只需要改一下配置中的开关量就能实现吗?
能否实现,这里卖个关子,先不说,介绍完了本篇博文后,细心的读者一定知道答案,或者说一定能明白能否做,怎么做!
第一大点:先介绍一下springboot中能够支持的或者说封装好的常用的条件注入的注解
1 @ConditionalOnBean
1.1 基本使用案例
@Component
@ConditionalOnBean(name="aBean")
public class BBean {
private final ABean aBean;
public BBean(ABean aBean) {
// ...
}
}
1.2 使用说明
只有当beang的名称为aBean存在的时候,才会注入BBean。
2 @ConditionalOnMissingBean
2.1 基本案例
@Bean
@ConditionalOnMissingBean(name = "notExistsBean")
public BeanToCreate createOneBean() {
return new BeanToCreate("notExistsBean");
}
2.2 使用说明
只有当bean名称为notExistsBean不存在的时候,BeanToCreate类型的bean才会被创建,和@ConditionalOnBean的使用方式相反
3 @ConditionalOnClass
3.1 基本使用案例
@Bean
@ConditionalOnClass(DependedClz.class)
public InjectIfClzExists injectIfClzExists() {
return new InjectIfClzExists("dependedClz");
}
3.2 使用说明
只有当Class为DependedClz.class存在的时候,才会注入类型为InjectIfClzExists的bean,使用上和@ConditionalOnBean有些类似。
4 @ConditionalOnMissingClass
4.1 使用案例
@Bean
@ConditionalOnMissingClass("com.shihuc.bean.clz.DependedClz")
public InjectIfClzNotExists injectIfClzNotExists() {
return new InjectIfClzNotExists("com.shihuc.bean.clz.DependedClz");
}
4.2 使用说明
只有当类com.shihuc.bean.clz.DependedClz不存在的时候,才会注入类型为InjectIfClzNotExists的bean。
5 @ConditionalOnProperty
5.1 基本使用案例
springboot的项目中配置文件application.properties文件中有如下配置:
#.....
section.condition_field=noti
section.condition_property=test
#...
@Bean
@ConditionalOnProperty("section.condition_field")
public PropertyExistBean propertyExistBean() {
return new PropertyExistBean("section.condition_field");
}
5.2 使用说明
主要是根据配置文件中的参数,来决定是否需要创建这个bean,这样就给了我们一个根据配置来控制Bean的选择的手段了,这个非常的好用。因为application.properties文件中存在section.condition_field这个属性,所以,PropertyExistBean这个bean会被创建出来。
5.3 扩展用法
5.3.1 注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty { /**
* Alias for {@link #name()}.
* @return the names
* 注意,这个value和name不能同时使用
*/
String[] value() default {}; /**
* A prefix that should be applied to each property. The prefix automatically ends
* with a dot if not specified.
* @return the prefix
*/
String prefix() default ""; /**
* The name of the properties to test. If a prefix has been defined, it is applied to
* compute the full key of each property. For instance if the prefix is
* {@code app.config} and one value is {@code my-value}, the full key would be
* {@code app.config.my-value}
* <p>
* Use the dashed notation to specify each property, that is all lower case with a "-"
* to separate words (e.g. {@code my-long-property}).
* @return the names
*/
String[] name() default {}; /**
* The string representation of the expected value for the properties. If not
* specified, the property must <strong>not</strong> be equal to {@code false}.
* @return the expected value
*/
String havingValue() default ""; /**
* Specify if the condition should match if the property is not set. Defaults to
* {@code false}.
* @return if should match if the property is missing
*/
boolean matchIfMissing() default false;
}
当我想实现配置文件中存在属性aaa.bbb且其属性的值为ccc时,才注入bean实例DDDD(名为dddd)。
@Bean("dddd")
@ConditionalOnProperty(value="aaa.bbbb", havingValue="ccc")
public DDDD propertyExistBean() {
return new DDDD("aaa.bbb");
}
6 @ConditionalOnExpression
6.1 使用案例
配置文件application.properties中存在下面的配置内容:
conditional.flag=true
java对应代码:
@Bean
@ConditionalOnExpression("#{'true'.equals(environment['conditional.flag'])}")
public ExpressTrueBean expressTrueBean() {
return new ExpressTrueBean("express true");
}
6.2 使用说明
相比较前面的Bean,Class是否存在,配置参数property是否存在或者有某个值而言,这个依赖SPEL表达式的,使用起来就功能显得更加强大了;其主要就是执行Spel表达式,根据返回的true/false来判断是否满足条件。
第二大点: spring基于Condition接口和@Conditional注解进行注入bean
这个相当于是条件注入bean的根源解决方案,上述其他几个ConditionalOnXXXX的注解,都是这个Conditional注解的具体场景的定制版,假如没有能够满足自己的应用场景的,或者说要自己实现一个比较特殊的条件注入呢,例如多个条件同时成立之类,怎么办呢,那就需要通过实现Condition接口然后基于@Conditional注解进行使用了。
1 @Conditional注解定义
//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition}s that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
注意,这个注解就一个参数value,且入参是一个Condition的Class的数组。
2 Condition是什么?
@FunctionalInterface
public interface Condition { /**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
3. 使用案例
假设属性配置文件中,有两个环境参数,一个是温度temp,一个是湿度humi,只有当温度高于30度,且湿度大于50个点时,启用Linux,当温度小于30度且湿度小于50个点时,启用Windows,这个只是为了说明在一个@Conditional里面将多个条件满足该如何实现,还有其他的业务场景,可以参照这个案例。
3.1 配置文件参数
#温度数据,摄氏温度
conditional.prop.temp=
#湿度数据,百分比,这里不带百分号,相当于扩大100倍,使用的时候除以100
conditional.prop.humi=
3.2 定义bean
有一个HeWoBean的接口,以及两个实现类HelloBean和WorldBean。
/**
* @Author: chengsh05
* @Date: 2019/8/29 16:17
*/
public interface HeWoBean { public String toString();
}
/**
* @Author: chengsh05
* @Date: 2019/8/29 15:52
*/
public class HelloBean implements HeWoBean { public String getHhh() {
return hhh;
} public void setHhh(String hhh) {
this.hhh = hhh;
} public String getEee() {
return eee;
} public void setEee(String eee) {
this.eee = eee;
} String hhh; String eee; public HelloBean(String hh, String ee) {
this.hhh = hh;
this.eee = ee;
} @Override
public String toString() {
return this.hhh + ", " + this.eee;
}
}
/**
* @Author: chengsh05
* @Date: 2019/8/29 15:54
*/
public class WorldBean implements HeWoBean {
public String getWww() {
return www;
} public void setWww(String www) {
this.www = www;
} public String getOoo() {
return ooo;
} public void setOoo(String ooo) {
this.ooo = ooo;
} String www;
String ooo; public WorldBean(String ww, String oo) {
this.www = ww;
this.ooo = oo;
} @Override
public String toString() {
return this.www + ", " + this.ooo;
}
}
3. condition接口实现类及@Conditional应用
/**
* @Author: chengsh05
* @Date: 2019/8/29 9:08
* @Description: 配置信息中,温度和湿度条件满足的时候,即当温度temp大于30度,湿度大于50%,启用Linux
*/
public class LinuxTime implements Condition { @Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String tempStr = context.getEnvironment().getProperty("conditional.prop.temp");
float temp = Float.valueOf(tempStr);
String humiStr = context.getEnvironment().getProperty("conditional.prop.humi");
float humi = Float.valueOf(humiStr);
if(temp > && humi > ){
return true;
}
return false;
}
}
/**
* @Author: chengsh05
* @Date: 2019/8/29 9:07
* @Description: 配置信息中,温度和湿度条件满足的时候,即当温度temp小于30度,湿度小于50%,启用windows
*/
public class WindowsTime implements Condition { @Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String tempStr = context.getEnvironment().getProperty("conditional.prop.temp");
float temp = Float.valueOf(tempStr);
String humiStr = context.getEnvironment().getProperty("conditional.prop.humi");
float humi = Float.valueOf(humiStr);
if(temp < && humi < ){
return true;
}
return false;
}
}
/**
* @Author: chengsh05
* @Date: 2019/8/29 15:50
*/
@Configuration
public class MyConditional { @Bean("mybean")
@Conditional(LinuxTime.class)
public HelloBean createHello() {
return new HelloBean("hello", "Linux");
} @Bean("mybean")
@Conditional(WindowsTime.class)
public WorldBean createWorld() {
return new WorldBean("world", "Windows");
}
}
4.应用验证
/**
* @Author: chengsh05
* @Date: 2019/8/29 16:03
*/
@Controller
@RequestMapping("/condition")
public class ConditionalController { @Autowired
@Qualifier("mybean")
private HeWoBean myBean; @RequestMapping("/check")
@ResponseBody
public void check() {
System.out.println("///||||\\\\ ==> " + myBean.toString());
}
}
分析来看,LinuxTime因为没有满足温度temp和湿度humi的条件(即在配置文件中的参数),所以,LinuxTime这个bean在MyConditional这个配置类中是不会被创建出来的,即最终HeHoBean这个就只有WorldBean被注入到spring容器了。打印的日志,也证实了这个。
///||||\\ ==> world, Windows
总结:
1. 基于springboot内置的条件注解,开发一些应用,基于某种条件进行bean的注入还是很方便的,基本可以解决大部分常见场景需求。
2. 基于内置的条件注入注解的组合使用,可以实现多条件约束的bean的注入需求,只有多个条件注入条件都成立时,对应的bean才会被注入到spring的容器。
3. 内置注解不管单独用还是组合使用,都不能搞定你的应用需求,那么可以选择实现condition接口,基于@Conditional注解来自己完成条件注入的需求了。
到这里,看官们,你是否有结论了,关于前面提到的,session共享模块,基于配置参数开关量,灵活切换模块工作在header模式还是cookie模式?答案是可以的,至于如何实现,结合我这里的介绍,是能得到答案的。
Spring生态研习【五】:Springboot中bean的条件注入的更多相关文章
- Spring生态研习【四】:Springboot+mybatis(探坑记)
这里主要是介绍在springboot里面通过xml的方式进行配置,因为xml的配置相对后台复杂的系统来说,能够使得系统的配置和逻辑实现分离,避免配置和代码逻辑过度耦合,xml的配置模式能够最大限度的实 ...
- Spring生态研习【一】:定时任务Spring-task
本系列具体研究一下spring生态中的重要或者常用的功能套件,今天从定时任务开始,主要是spring-task.至于quartz,下次找个时间再总结. 我的验证环境,是SpringCloud体系下,基 ...
- Spring生态研习【二】:SpEL(Spring Expression Language)
1. SpEL功能简介 它是spring生态里面的一个功能强大的描述语言,支在在运行期间对象图里面的数据查询和数据操作.语法和标准的EL一样,但是支持一些额外的功能特性,最显著的就是方法调用以及基本字 ...
- 如果你还不知道如何控制springboot中bean的加载顺序,那你一定要看此篇
1.为什么需要控制加载顺序 springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题.在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功 ...
- Spring基础——在 IOC 容器中 Bean 之间的关系
一.在 Spring IOC 容器中 Bean 之间存在继承和依赖关系. 需要注意的是,这个继承和依赖指的是 bean 的配置之间的关系,而不是指实际意义上类与类之间的继承与依赖,它们不是一个概念. ...
- Spring boot 入门五:springboot 开启声明式事务
springboot开启事务很简单,只需要一个注解@Transactional 就可以了.因为在springboot中已经默认对jpa.jdbc.mybatis开启了事务.这里以spring整合myb ...
- spring中bean配置和注入场景分析
bean与spring容器的关系 Bean配置信息定义了Bean的实现及依赖关系,Spring容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载.实例化Bean,并 ...
- spring boot 学习(五)SpringBoot+MyBatis(XML)+Druid
SpringBoot+MyBatis(xml)+Druid 前言 springboot集成了springJDBC与JPA,但是没有集成mybatis,所以想要使用mybatis就要自己去集成. 主要是 ...
- Spring源码之Springboot中监听器介绍
https://www.bilibili.com/video/BV12C4y1s7dR?p=11 监听器模式要素 事件 监听器 广播器 触发机制 Springboot中监听模式总结 在SpringAp ...
随机推荐
- PHP fsockopen 异步写入文件
b.php <?php $url = 'http://fsc.com/a.php'; $param = array( 'name'=>'fdipzone', 'gender'=>'m ...
- Centos6.5配置防火墙
1.查看防火墙状态 [root@instance-xfl1djr7 ~]# /etc/init.d/iptables status 2.启动/关闭防火墙 开启防火墙 [root@instance-xf ...
- H3C CSMA/CA
- Linux运维技术之yum与rpm的基本使用要点
https://pkgs.org/ 与https://rpmfind.org/ RPM包下载 RPM包简介 1.安装与升级时,使用的是包全名 2.RPM包安装时要注意包的依赖性 RPM包操作(系统 ...
- Beta冲刺第3次
一.团队成员的学号姓名列表 学号 姓名 201731103226 翟仕佶 201731062517 曾中杰 201731062424 杨模 201731062632 邓高虎 201731062624 ...
- Python3如何上传自己的PyPI项目
有过一定的 Python 经验的开发者都知道,当引入第三方包时,我们常常会使用 pip install 命令来下载并导入包. 那么,如何写一个自己的包,上传到 PyPI 呢,其他开发者也可以通过 pi ...
- Java8的Stream API使用
前言 这次想介绍一下Java Stream的API使用,最近在做一个新的项目,然后终于可以从老项目的祖传代码坑里跳出来了.项目用公司自己的框架搭建完成后,我就想着把JDK版本也升级一下吧(之前的项目, ...
- 微信之通过AppID和AppSecret获取access_token
最近在搞微信公众平台这方面的东西,,但实际使用的时候发现和access_token有关的接口都无法正常调用,于是debug了下,发现获取到了AppID和AppSecret,在最后请求access_to ...
- Laravel5.4框架中视图共享数据的方法详解
本文实例讲述了Laravel5.4框架中视图共享数据的方法.分享给大家供大家参考,具体如下: 每个人都会遇到这种情况:某些数据还在每个页面进行使用,比如用户信息,或者菜单数据,最基本的做法是在每个视图 ...
- Ribbon自带负载均衡策略
IRule这是所有负载均衡策略的父接口,里边的核心方法就是choose方法,用来选择一个服务实例. AbstractLoadBalancerRuleAbstractLoadBalancerRule是一 ...