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

  1. name和value不能同时存在.也不能同时不存在. 两者只能存在一个
  2. 如果havingValue存在, 则跟havingValue的值进行比较, 相同返回true, 不同返回false
  3. 如果没有指定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. 此时加载被修饰的类或方法
  4. 在配置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;

}

三. 代码验证

  1. 验证: 只配置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;

/**
 * <Description> <br>
 *
 * @author xw<br>
 * @version 1.0<br>
 * @taskId <br>
 * @createDate 2020/9/25 <br>
 * @see com.example.learn.learnspring.annotation <br>
 * @since <br>
 */
@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不能同时存在, 也不能都不存在的情况

  1. 验证都不存在的情况
    代码:
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<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
            metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
      List<ConditionMessage> noMatch = new ArrayList<>();
      List<ConditionMessage> 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<String> missingProperties = new ArrayList<>();
       List<String> 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<String, Object> 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<String> missing, List<String> 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)——包

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐