Spring注解系列之ConditionalOnProperty

Spring注解系列之ConditionalOnProperty

一、简介

Spring Boot源码中大量使用@ConditionalOnProperty来控制Configuration是否生效。

二、源码

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

	/**
	 * 数组,获取对应property名称的值,与name不可同时使用。
	 * 
	 * Alias for {@link #name()}.
	 * @return the names
	 */
	String[] value() default {};

	/**
	 * property名称的前缀,可有可无,根据事情情况进行配置。
	 * 前缀将会应用于每个属性的前缀。如果未指定,前缀将自动结束.
	 * 
	 * 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 "";

	/**
	 * 数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用。
	 * 
	 * 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 fully key would be
	 * {@code app.config.my-value}
	 * <p>
	 * 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 {};

	/**
	 * 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置。
	 * 
	 * The string representation of the expected value for the properties. If not
	 * specified, the property must <strong>not</strong> be equals to {@code false}.
	 * @return the expected value
	 */
	String havingValue() default "";

	/**
	 * 缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错
	 * 
	 * 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;

}

单纯看上面的说明还是很抽象,下面我们举一些例子来帮助我们理解。

三、实战

3.1 name的用法

基础类

public class User {
}
public class Dept {
}

配置类

// 意思:当配置文件中只要出现config1.enable时(内容为空,同样也是有效的),则该配置生效。
@Configuration
@ConditionalOnProperty(name = "config1.enable")
public class Config1 {

    @Bean
    public User user() {
        System.out.println("------------------初始化User----------------");
        return new User();
    }
}
// 意思:当配置文件中只要出现config2.enable时(内容为空,同样也是有效的),则该配置生效。
@Configuration
@ConditionalOnProperty(name = "config2.enable")
public class Config2 {

    @Bean
    public Dept dept() {
        System.out.println("------------------初始化Dept----------------");
        return new Dept();
    }
}

启动类

@SpringBootApplication
public class ConditionApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConditionApplication.class, args);
    }
}

配置文件

config1:
  enable: test

启动效果

根据上述配置文件的配置,我们看到Config1配置生效了,并打印了创建了User这个Bean的日志。而Config2则没有打印,也就意味着其没有生效。

在这里插入图片描述

3.2 根据配置的开关进行动态加载配置(havingValue的使用)

在上述代码的基础上,我们首先的Config1Config2中稍作一些修改。

代码

// 当配置文件中config1.enable的值为true才加载,否则就不加载
@Configuration
@ConditionalOnProperty(name = "config1.enable", havingValue = "true")
public class Config1 {

    @Bean
    public User user() {
        System.out.println("------------------初始化User----------------");
        return new User();
    }
}
// 当配置文件中config1.enable的值为true才加载,否则就不加载
@Configuration
@ConditionalOnProperty(name = "config2.enable", havingValue = "true")
public class Config2 {

    @Bean
    public Dept dept() {
        System.out.println("------------------初始化Dept----------------");
        return new Dept();
    }
}

配置文件

config1:
  enable: false
config2:
  enable: true

效果

我们发现User没有初始化,而Dept则进行了初始化。

在这里插入图片描述

3.3 批量和单个配置结合

随着功能的迭代,我们又更多类似的初始化功能,例如创建User、Dept、Menu、Role。依样画葫芦,application.yml中多了相似的配置。

config1:
  enable: false
config2:
  enable: true
config3:
  enable: true
# 等等...

这样的配置有很大的优化空间,我们可能需要一个的创建的功能,同时也要保留原来批量创建的功能。

形如下面

config:
  all: true

这个有点麻烦,涉及到多个条件的组合。ConditionalOnProperty是支持 “多个条件逻辑与”的。

@ConditionalOnProperty(name={"config1.enable","config.all"}, havingValue="true")

这样的条件是必须都是true才会加载。限制很大。

而我们实际项目往往需要的,只要有一个是true则就进行加载。所以此时ConditionalOnProperty并不能解决我们的问题。

注意: 1:ConditionalOnProperty 本身也并没有这样的功能
2: 一个类的头部不能添加多个ConditionalOnProperty,所以这种方式是不可能的
3:一种繁琐的做法,是自定义条件,继承AnyNestedCondition(这里不做探讨,感兴趣的同学可以去了解下)
4:使用@ConditionalOnExpression注解

代码如下

@ConditionalOnExpression(value = "${config2.enable:false} || ${config.all:false}")

这样既实现了其中两个值,只要有一个值是true,就会进行加载Config配置类。

但是这样做还是有问题。我们可以其看成局部配置和全部配置的关系处理。想要的达到的效果如下:

如果局部有配置,则按局部配置处理。如果局部没配置,则按全局配置处理。如果全局、局部都没配置,则默认不导入。这里可以利用“默认值”嵌套表达式实现

其实这样的效果,往往是我们实际项目需要的比较多的使用场景。

所以,嵌套表达式闪亮登场。

@ConditionalOnExpression(value = "${config2.enable:${config.all:false}}")

感兴趣的同学可以,修改配置文件的代码,启动起来看看效果,我在这里就不演示了。

发布了158 篇原创文章 · 获赞 147 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/weixin_39723544/article/details/100652275