spring4.0之五:@Conditional在满足特定条件下,才会实例化对象,@ConditionalOnProperty @ConditionalOnExpression
一、在Spring的早期版本你可以通过以下方法来处理条件问题
1.1、Spring Expression Language(SPEL)
1.2、使用Profile
二、@Conditional介绍
三、@Conditional使用
四、多属性值
4.1、@ConditionalOnProperty的多属性值
4.2.@ConditionalOnExpression 表达式条件注入
4.3、参考@ConditionalOnProperty自定义注解实现多条件判断
这篇文章介绍Spring 4的@Conditional注解。
一、在Spring的早期版本你可以通过以下方法来处理条件问题
- 3.1之前的版本,使用Spring Expression Language(SPEL)。
- 3.1版本有个新特性叫profile,用来解决条件问题。
1.1、Spring Expression Language(SPEL)
SPEL有一个三元运算符(if-then-else)可以在配置文件中当作条件语句,如下:
<bean id="flag"> <constructor-arg value="#{systemProperties['system.propery.flag'] ?: false }" /> </bean> <bean id="testBean"> <property name="prop" value="#{ flag ? 'yes' : 'no' }"/> </bean>
testBean的prop动态依赖于flag的值。
1.2、使用Profile
<!-- 如果没有设置profile,default.xml将被加载 --> <!-- 必须放置在配置文件的最底下,后面再也没有bean的定义 --> <beans profile="default"> <import resource="classpath:default.xml" /> </beans> <!-- some other profile --> <beans profile="otherProfile"> <import resource="classpath:other-profile.xml" /> </beans>
二、@Conditional介绍
官方文档定义:“Indicates that a component is only eligible for registration when all specified conditions match”,意思是只有满足一些列条件之后创建一个bean。
除了自己自定义Condition之外,Spring还提供了很多Condition给我们用
@ConditionalOnClass : classpath中存在该类时起效
@ConditionalOnMissingClass : classpath中不存在该类时起效
@ConditionalOnBean : DI容器中存在该类型Bean时起效
@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
@ConditionalOnExpression : SpEL表达式结果为true时
@ConditionalOnProperty : 参数设置或者值一致时起效
@ConditionalOnResource : 指定的文件存在时起效
@ConditionalOnJndi : 指定的JNDI存在时起效
@ConditionalOnJava : 指定的Java版本存在时起效
@ConditionalOnWebApplication : Web应用环境下起效
@ConditionalOnNotWebApplication : 非Web应用环境下起效
@Conditional定义
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE, ElementType.METHOD) public @interface Conditional{ Class<? extends Condition>[] value(); }
@Conditional注解主要用在以下位置:
-
类级别可以放在注标识有@Component(包含@Configuration)的类上
- 作为一个meta-annotation,组成自定义注解
- 方法级别可以放在标识由@Bean的方法上
如果一个@Configuration的类标记了@Conditional,所有标识了@Bean的方法和@Import注解导入的相关类将遵从这些条件。
condition接口定义如下:
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 registration. */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
示例1:下面看一个例子:
结果:
示例2:@ConditionalOnProperty来控制Configuration是否生效
Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用 String prefix() default "";//property名称的前缀,可有可无 String[] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用 String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置 boolean matchIfMissing() default false;//缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错 boolean relaxedNames() default true;//是否可以松散匹配,至今不知道怎么使用的 } }
通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。
如果该值为空,则返回false;
如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
如果返回值为false,则该configuration不生效;为true则生效。
@Configuration //在application.properties配置"mf.assert",对应的值为true @ConditionalOnProperty(prefix="mf",name = "assert", havingValue = "true") public class AssertConfig { @Autowired private HelloServiceProperties helloServiceProperties; @Bean public HelloService helloService(){ HelloService helloService = new HelloService(); helloService.setMsg(helloServiceProperties.getMsg()); return helloService; } }
三、@Conditional使用
ConditionalOnProperty的参数详细用法
通过属性值来控制configuration是否生效
@Configuration @ConditionalOnProperty(name="config.enabled",havingValue = "true") public class ConfigBean { //... }
// 属性设置
config.enabled=true
3.1、单一属性
(不设置前缀 prefix )
name指定属性名
havingValue 指定属性的值
matchIfMissing 没有指定属性的时候,是否启用configuration,默认不启用
-
设置属性,优先根据属性值去匹配
-
没设置属性,看 matchIfMissing 的值
matchIfMissing default false
四、多属性值
设置前缀 prefix, 并且必须设置 name 或者value
4.1、@ConditionalOnProperty的多属性值
这以两个值为例:
@Configuration @ConditionalOnProperty(prefix="self",name={"a","b"},havingValue = "true") public class ConfigBean { //... }
// 属性设置
self.a=true
self.b=true
-
都满足
即:
self.a=true
self.b=true
则生效 -
任何一个属性不满足
即:
self.a=true
self.b=false
或者
即:
self.a=abc
self.b=true
则不生效 -
一个满足 一个不设置
即:
self.a=true
或者
self.b=true
此时看matchIfMissing
为true则生效,否则不生效
4.2.@ConditionalOnExpression 表达式条件注入
下面介绍的注解将更加的灵活,基于SPEL表达式的条件注解ConditionalOnExpression
相比较前面的Bean,Class是否存在,配置参数是否存在或者有某个值而言,这个依赖SPEL表达式的,就显得更加的高级了;其主要就是执行Spel表达式,根据返回的true/false来判断是否满足条件
至于SPEL是什么东西,SpEL(Spring Expression Language),即Spring表达式语言。下面以一个简单的demo
4.2.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"; }
4.2.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
@ConditionalOnExpression("'true") 当括号中的内容为true时,使用该注解的类被实例化,支持语法如下: @ConditionalOnExpression("${mq.cumsumer.enabled}==1&&${rabbitmq.comsumer.enabled:true}") @ConditionalOnExpression("'${mq.comsumer}'.equals('rabbitmq')")
4.3、参考@ConditionalOnProperty自定义注解实现多条件判断
需求:当下面2个配置项都为true时,
pool.sync.enable: true pool.monitor:true
方式1:@ConditionalOnProperty(name = { "pool.sync.enable", "pool.monitor" }, havingValue = "true", matchIfMissing = false)
或者
方式2:
才会创建ThreadPoolMonitor.java的bean。下面的代码实现:
import java.lang.annotation.*; import org.springframework.context.annotation.Conditional; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(ThreadMonitorOnPropertyCondition.class) public @interface ThreadMonitorConditionalOnProperty { String enable(); String enableValue(); String monitor(); String monitorValue(); }
2、
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; class ThreadMonitorOnPropertyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String name = attribute(metadata, "enable"); String enableValue = attribute(metadata, "enableValue"); String enableRealValue = getProperty(context, name); if (enableValue.equals(enableRealValue)) { String monitorName = attribute(metadata, "monitor"); String monitorValue = attribute(metadata, "monitorValue"); String monitorRealValue = getProperty(context, monitorName); if (monitorValue.equals(monitorRealValue)) { return true; } } return false; } /** * 获取注解中的值 * @param metadata * @param name * @return */ private String attribute(AnnotatedTypeMetadata metadata, String name) {
if(!metadata.isAnnotated(MySQLDatasourceConditionalOnProperty.class.getName())) {
return null;
}
return (String) metadata.getAnnotationAttributes(ThreadMonitorConditionalOnProperty.class.getName()).get(name); } /** * 获取配置变量值 * @param context * @param name * @return */ private String getProperty(ConditionContext context, String name) { return context.getEnvironment().getProperty(name); } }
3、测试
@Configuration @ThreadMonitorConditionalOnProperty(enable = "pool.sync.enable",enableValue = "true", monitor = "pool.monitor", monitorValue = "true") public class ThreadPoolMonitor {
配置文件:
pool.monitor: true #sync pool pool.sync.enable: true pool.sync.corePoolSize: 10 pool.sync.maxPoolSize: 50 pool.sync.keepAliveSeconds: 60 pool.sync.queueCapacity: 0