@ConditionalOnProperty的用法以及处理@ConditionalOnProperty的源码分析

您所在的位置:网站首页 @conditionalonproperty注解 @ConditionalOnProperty的用法以及处理@ConditionalOnProperty的源码分析

@ConditionalOnProperty的用法以及处理@ConditionalOnProperty的源码分析

2023-04-01 05:39| 来源: 网络整理| 查看: 265

一. @ConditionalOnProperty的作用

在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的具体使用规则.

name和value不能同时存在.也不能同时不存在. 两者只能存在一个如果havingValue存在, 则跟havingValue的值进行比较, 相同返回true, 不同返回false如果没有指定havingValue, 则用prefix + name 或者prefix + value获取配置项的值, 然后跟 "false"字符串比较, 相同返回false, 不同返回true 相同: 即配置项值为false时, 例journal.jnc.enabled=false, 此时是不加载被修饰的类或方法 不同: 即配置项值为非false的任何值, 例journa.jnc.enabled=true / journa.jnc.enabled=123 / journa.jnc.enabled= 等, 都是返回true. 此时加载被修饰的类或方法在配置matchIfMissing后, 如果prefix + name 或者prefix + value 都不存在, 则以matchIfMissing的值为准. matchIfMissing = true, 则加载 matchIfMissing = false, 则不加载

以下时@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; } 三. 代码验证 验证: 只配置name属性时的加载情况 配置项: application.properties中的内容 learn.conditionalOnProperty.enabled=

代码:

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, 只能同时选择一个使用. 随便选择哪一个. 功能上是没有任何区别的.

四. 处理@ConditionalOnProperty的源码阅读

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)——包



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3