在spring中有时需要根据配置项来控制某个类或者某个bean是否需要加载.这个时候就可以通过@ConditionnalOnProperty来实现.
@ConditionalOnProperty 可以用在类或者方法上.
例:
// 用在类上
// org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.java代码片段
/**
* {@link AnyNestedCondition} that checks that either {@code spring.datasource.type}
* is set or {@link PooledDataSourceAvailableCondition} applies.
*/
static class PooledDataSourceCondition extends AnyNestedCondition {
@ConditionalOnProperty(prefix = "spring.datasource", name = "type")
static class ExplicitType {
}
}
/**
* 2. 用在方法上
* RBC database
*
* @param druidDataSourceProperties properties
* @return DataSource
*/
@Bean(name = "jncDataSource", initMethod = "init", destroyMethod = "close")
@ConditionalOnMissingBean(name = "jncDataSource")
@ConditionalOnProperty(prefix = JOURNAL_JNC_PREFIX, value = "enabled", matchIfMissing = true)
@ConfigurationProperties(prefix = JOURNAL_JNC_PREFIX)
public CoreDataSourceBean jncDataSource(
@Qualifier("jncDruidDataSourceProperties") DruidDataSourceProperties druidDataSourceProperties) {
DruidDataSource druidDataSource = new DruidDataSourceBuilder().properties(druidDataSourceProperties).build();
if (druidDataSourceProperties.getUrl().contains(SYMBOL)) {
return new RouteDataSourceBean(druidDataSource);
}
else {
return new CoreDataSourceBean(druidDataSource);
}
}
先介绍一下@ConditionalOnProperty的具体使用规则.
以下时@ConditionalOnProperty的源码
package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;
// 指定生命周期
@Retention(RetentionPolicy.RUNTIME)
// 指定作用目标
@Target({ ElementType.TYPE, ElementType.METHOD })
// 说明注解会被包含在javadoc中
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
/**
* 指定配置项前缀, 以journal.jnc.enabled=true为例
* 可以指定prefix = journal.jnc作为配置项前缀, 在取配置项时, 就只会取以journal.jnc开头的配置
*/
String prefix() default "";
/**
* 配置项的中的属性, 以journal.jnc.enabled=true为例
* value = enabled
*/
String[] value() default {};
/**
* name同样是配置项的属性, 在spring新的版本中, name和value没区别
* name = enabled
*/
String[] name() default {};
/**
* 指定配置项的值, 以journal.jnc.enabled=true为例
* havingValue = true, 默认是空
*/
String havingValue() default "";
/**
* 如果配置项不存在时, 可以根据该属性来判断是否加载类或者bean
* 默认为false.
* 特别注意: 该属性生效的前提是, 配置项不存在的情况下.
*/
boolean matchIfMissing() default false;
}
代码:
package com.example.learn.learnspring.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class LearnConditionalOnProperty {
@Autowired
private Environment environment;
@Bean
@ConditionalOnProperty(prefix = "learn.conditionalOnProperty", name = "enabled")
public void testNameProperty() {
String value = environment.getProperty("learn.conditionalOnProperty.enabled");
System.out.println("Property[name]验证");
System.out.println("learn.conditionalOnProperty.enabled = " + value);
System.out.println("Property[name]验证");
}
}
此时, 根据上面的规则, 在只指定name的情况下, 符合第三点, 根据prefix + name的值和false比较是否相等
因此, testNameProperty()方法会被执行, 控制台打印 - 加载某个bean - 这个信息
从运行结果来看, testNameProperty()方法确实是执行了
当learn.conditionalOnProperty.enabled=false时, testNameProperty()方法就不再执行. 可以自行试一下
2. 验证: 只配置value属性时的情况
为了与1区别, 配置项将设置其他值,如123
配置项: application.properties
learn.conditionalOnProperty.enabled=123
代码:
package com.example.learn.learnspring.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
*
*
* @author xw
* @version 1.0
* @taskId
* @createDate 2020/9/25
* @see com.example.learn.learnspring.annotation
* @since
*/
@Configuration
public class LearnConditionalOnProperty {
@Autowired
private Environment environment;
@Bean
@ConditionalOnProperty(prefix = "learn.conditionalOnProperty", value = "enabled")
public void testValueProperty() {
String value = environment.getProperty("learn.conditionalOnProperty.enabled");
System.out.println("Property[value]验证");
System.out.println("learn.conditionalOnProperty.enabled = " + value);
System.out.println("Property[value]验证");
}
}
根据第三点规则, 可以预见, testValueProperty()方法会被执行
同样的,当learn.conditionalOnProperty.enabled=false时, testValueProperty()方法就不再执行. 可以自行试一下
3. 验证: havingValue的情况
配置项:application.properties
learn.conditionalOnProperty.enabled=test
代码: havingValue=123
package com.example.learn.learnspring.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class LearnConditionalOnProperty {
@Autowired
private Environment environment;
@Bean
@ConditionalOnProperty(prefix = "learn.conditionalOnProperty", value = "enabled", havingValue = "123")
public void testHavingValueProperty() {
String value = environment.getProperty("learn.conditionalOnProperty.enabled");
System.out.println("Property[havingValue]验证");
System.out.println("learn.conditionalOnProperty.enabled = " + value);
System.out.println("Property[havingValue]验证");
}
}
根据第三点规则可以得到, testHavingValueProperty()方法不会被执行.
此时, 程序的确没有打印任何关于testHavingValueProperty()方法的信息
同样, 使用name属性与value属性的结果一致.
4. 验证: matchIfMissing的情况
配置项application.properties
#不配置项任何东西
#learn.conditionalOnProperty.enabled=test
代码:
package com.example.learn.learnspring.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class LearnConditionalOnProperty {
@Autowired
private Environment environment;
@Bean
@ConditionalOnProperty(prefix = "learn.conditionalOnProperty", matchIfMissing = true)
public void testMatchIfMissingProperty() {
String value = environment.getProperty("learn.conditionalOnProperty.enabled");
System.out.println("Property[matchIfMissing]验证");
System.out.println("learn.conditionalOnProperty.enabled = " + value);
System.out.println("Property[matchIfMissing]验证");
}
}
直接看运行结果:
虽然没有配置learn.conditionalOnProperty.enabled, 但testMatchIfMissingProperty方法依旧被执行了
5.验证:name和value不能同时存在, 也不能都不存在的情况
package com.example.learn.learnspring.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class LearnConditionalOnProperty {
@Autowired
private Environment environment;
@Bean
@ConditionalOnProperty(prefix = "learn.conditionalOnProperty")
public void testNameAndValueProperty() {
String value = environment.getProperty("learn.conditionalOnProperty.enabled");
System.out.println("learn.conditionalOnProperty.enabled = " + value);
}
}
直接看运行结果:
发现报错了, 提示java.lang.IllegalStateException: The name or value attribute of @ConditionalOnProperty must be specified
意思就是 name或value必须被指定, 所以name和value不能都不存在
2) 验证都存在的情况
代码:
package com.example.learn.learnspring.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class LearnConditionalOnProperty {
@Autowired
private Environment environment;
@Bean
@ConditionalOnProperty(prefix = "learn.conditionalOnProperty", name = "enabled", value = "enabled")
public void testNameAndValueProperty() {
String value = environment.getProperty("learn.conditionalOnProperty.enabled");
System.out.println("Property[]验证");
System.out.println("learn.conditionalOnProperty.enabled = " + value);
System.out.println("Property[]验证");
}
}
运行后发线, 依然报错
提示信息是:java.lang.IllegalStateException: The name and value attributes of @ConditionalOnProperty are exclusive
说明: name和value也不能同时存在.
结论:
name和value, 只能同时选择一个使用. 随便选择哪一个. 功能上是没有任何区别的.
Springboot版本:2.3.4.RELEASE
首先是看OnPropertyCondition.java类的getMatchOutcome()方法
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取所有的注解, 这里只有一个,因为项目中就只配置了一个
List allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List noMatch = new ArrayList<>();
List match = new ArrayList<>();
// 遍历
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
// 核心方法, 重点关注
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
// ...省略其他代码
}
接下来看determineOutcome()方法的源码
class OnPropertyCondition extends SpringBootCondition {
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
//
Spec spec = new Spec(annotationAttributes);
List missingProperties = new ArrayList<>();
List nonMatchingProperties = new ArrayList<>();
spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
if (!missingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
}
if (!nonMatchingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.found("different value in property", "different value in properties")
.items(Style.QUOTE, nonMatchingProperties));
}
return ConditionOutcome
.match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}
private static class Spec {
// 有四个属性, 与ConditionalOnProperty对应
private final String prefix;
private final String havingValue;
// names要么是value的值, 要么是name的值
private final String[] names;
//
private final boolean matchIfMissing;
// 构造函数
Spec(AnnotationAttributes annotationAttributes) {
String prefix = annotationAttributes.getString("prefix").trim();
if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
prefix = prefix + ".";
}
this.prefix = prefix;
this.havingValue = annotationAttributes.getString("havingValue");
// 关键
this.names = getNames(annotationAttributes);
this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
}
private String[] getNames(Map annotationAttributes) {
String[] value = (String[]) annotationAttributes.get("value");
String[] name = (String[]) annotationAttributes.get("name");
// 这里就是限制value或name必须指定
Assert.state(value.length > 0 || name.length > 0,
"The name or value attribute of @ConditionalOnProperty must be specified");
// 这个判断限制value和name不能同时存在
Assert.state(value.length == 0 || name.length == 0,
"The name and value attributes of @ConditionalOnProperty are exclusive");
// 取值
return (value.length > 0) ? value : name;
}
private void collectProperties(PropertyResolver resolver, List missing, List nonMatching) {
// 遍历name属性或value属性配置的值
for (String name : this.names) {
// 拼接配置项, 例: key = learn.conditionalOnProperty.enabled
String key = this.prefix + name;
if (resolver.containsProperty(key)) {
// 关键
if (!isMatch(resolver.getProperty(key), this.havingValue)) {
nonMatching.add(name);
}
}
else {
if (!this.matchIfMissing) {
missing.add(name);
}
}
}
}
// value: 配置项对应的值
// requiredValue : 为havingValue对应的值
private boolean isMatch(String value, String requiredValue) {
// requiredValue存在的情况
if (StringUtils.hasLength(requiredValue)) {
// 直接和requiredValue比较, 不区分大小写
return requiredValue.equalsIgnoreCase(value);
}
// requiredValue不存在的情况, 和false的字符串比较
// 这也是为什么name, value不配置值的情况下, 类依然会被加载的原因
return !"false".equalsIgnoreCase(value);
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("(");
result.append(this.prefix);
if (this.names.length == 1) {
result.append(this.names[0]);
}
else {
result.append("[");
result.append(StringUtils.arrayToCommaDelimitedString(this.names));
result.append("]");
}
if (StringUtils.hasLength(this.havingValue)) {
result.append("=").append(this.havingValue);
}
result.append(")");
return result.toString();
}
}
}
Spec是OnPropertyCondition的静态内部类, 也是规则判断的核心. 至此,@ConditionalOnProperty的所有内容都已结束
补充:
@Target:注解的作用目标
@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包