在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}
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 not 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【】 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=29
#湿度数据,百分比,这里不带百分号,相当于扩大100倍,使用的时候除以100
conditional.prop.humi=51
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 //代码效果参考:http://www.jhylw.com.cn/415925261.html
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 > 30 && humi > 60){
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 < 30 && humi < 60){
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