Spring Boot 条件配置@Conditional的秘密

使用Spring Boot开发项目的时候,有时候也会遇到不同的条件加载不同的配置,但是这里的条件不一定是指不同的环境的配置,也可以是系统环境等等。接下来我们就探究一下Spring Boot是给我们提供的@Profile相关的实现,以及一个重要的注解@Conditional的自定义实现。

演示环境

  • IntelliJ IDEA 2018.2.1 (Community Edition)
  • Maven 3.5.4
  • Spring Boot 2.1.1.RELEASE

简单实现

使用Spring Boot开发项目时,我们会根据开发、测试和生产等不同的环境来编写不同的application-xx.properties配置文件。Spring Boot给我们提供了一个@Profile注解,可以用来标注我们根据不同的环境来配置的配置类或者javaBean等等,达到控制它们的作用环境的作用,即实现条件配置。

接着我们编写一个bootstrap来演示一下@Profile的使用:

/**
 * 基于{@link Profile}实现条件配置
 *
 * @author Jerome Zhu
 */
public class ProfilesBootstrap {

    @Bean(name = "hello")
    @Profile("prod")
    public String prodHello() {
        System.out.println("profiles: prod");
        return "hello jerome, this is prod.";
    }

    @Bean(name = "hello")
    @Profile("test")
    public String testHello() {
        System.out.println("profiles: test");
        return "hello jerome, this is test.";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(ProfilesBootstrap.class)
                .web(WebApplicationType.NONE)
                .profiles("prod")
                .run(args);
        String helloBean = context.getBean("hello", String.class);
        System.out.println("hello Bean: " + helloBean);
        context.close();
    }

}

首先看代码,我们配置了两个javaBean,它们的名字都是hello,这里这两个javaBean都被标注了@Prifile,其中@Profile("prod")表示当激活prod环境时这个javaBean才会被注册,而@Profile("test")表示当激活test环境时这个javaBean会被注册到容器中。

接着通过SpringApplicationBuilder来配置我们的Spring Boot应用并启动,使用SpringApplicationBuilder提供的profiles方法来激活我们的环境。

并通过应用上下文根据javaBean的名字hello来获取当前环境中的javaBean,并输出来验证。

当我们使用profiles("prod")的时候,会发现控制台会有这样的输出:

profiles: prod
hello Bean: hello jerome, this is prod.

当我们使用profiles("test")的时候,会发现控制台会有这样的输出:

profiles: test
hello Bean: hello jerome, this is test.

这里就表示我们的配置是成功的。这里@Profile也可以与其他组件一起使用,比如:@Configuration@Service等等。

走进源码

根据上面的一个简单的例子,我们实现了根据激活的不同的profiles作为条件去加载不同的组件。接着进入源码一探究竟。首先进入@Profile的代码:

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

	/**
	 * The set of profiles for which the annotated component should be registered.
	 */
	String[] value();

}

根据注释可以看出,我们可以根据指定的profiles来注入我们的组件。这里value的类型是一个数组,说明我们可以同时指定多个。接着我们还可以看到@Conditional(ProfileCondition.class)这个注解,我们先跟到@Conditional里面看:

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

	/**
	 * All {@link Condition Conditions} that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

根据注释和代码,我们可以看出@Conditionalvalue值必须是一个Condition类的子类的Class,然后根据Condition#matches方法来判断是否匹配条件,根据判决条件的结果觉得是否要注册这个组件。接着我们进入ProfileCondition.class的代码中:

class ProfileCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
		if (attrs != null) {
			for (Object value : attrs.get("value")) {
				if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
					return true;
				}
			}
			return false;
		}
		return true;
	}

}

看到这儿我们就好像明白了写什么,首先ProfileCondition实现了Condition接口,并且重写了matches方法来判断是否匹配我们的条件。接着根据注解元数据AnnotatedTypeMetadata获取当前标注@Profile的注解元数据,如果这里attrs就为空直接返回true;如果不为空就获取指定的@Profile的值,在根据上下文获取环境,根据环境来判断当前环境是否与@Profile指定的环境一样,如果一样就返回true注册组件。

自定义实现

Spring Boot中有默认实现了很多@ConditionalOn***,它们最后使用的还是@ConditionalCondition接口,接下来我们就自定义实现一个@ConditionalOnSystemProperty体验以下条件配置的魅力。介绍以下@ConditionalOnSystemProperty的作用,我们注解中传入两值,一个是系统环境中变量的key和一个我们指定的这个key的值,然后我们拿到这个key去获取系统中对应的value,判断是否符合我们的条件来决定是否注册组件。

首先看@ConditionalOnSystemProperty代码:

/**
 * 根据系统环境条件判断是否装配Bean
 *
 * @author Jerome Zhu
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {

    /**
     * system property key
     */
    String name();

    /**
     * system property key
     */
    String value();
}

这个注解需要指定两个值keyvalue,用于我们后面的判断系统属性。

接着看OnSystemPropertyCondition代码的编写:

/**
 * {@link Condition} 实现匹配系统配置的条件
 *
 * @author Jerome Zhu
 */
public class OnSystemPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());

        String propertyKey = String.valueOf(attributes.get("key"));

        String propertyValue = String.valueOf(attributes.get("value"));

        // 获取系统变量 propertyKey 对应的值
        String sysPropertyValue = System.getProperty(propertyKey);

        return propertyValue.equals(sysPropertyValue);
    }
}

我们这里实现了Condition接口,重写了matches方法,根据我们的注解获取注解属性keyvalue的值。然后使用System#getProperty()方法来获取系统中的值,最后返回匹配结果。

最后我们编写一个bootstrap来验证我们自定义的条件配置的正确性:

/**
 * 验证自定义基于编程{@link Condition}方式的条件装配
 *
 * @author Jerome Zhu
 */
public class ConditionBootstrap {

    @Bean("hello")
    @ConditionalOnSystemProperty(key = "user.name", value = "user")
    public String userHello() {
        return "hello user !";
    }

    @Bean("hello")
    @ConditionalOnSystemProperty(key = "user.name", value = "jerome")
    public String jeromeHello() {
        return "hello jerome !";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);

        String helloBean = context.getBean("hello", String.class);
        System.out.println("hello Bean: " + helloBean);
        context.close();
    }

}

我们这里还是使用系统属性中的user.name作为判断条件,如果条件符合就注册以hello为名字的Bean。最后还是根据上下文获取该Bean,判断是否成功。启动程序在控制台会看到这样的输出:

hello Bean: hello user !

这里说明我们的基于@Conditional的条件配置完成了。

我当前电脑的user.name=user所有没有加载下面配置的那个Bean

总结

我们首先通过Spring Boot提供的@Profile来实现条件配置,接着又根据@Profile一步一步去了解源码,直到发现了@ConditionalCondition。最后我们使用@ConditionalCondition实现了我们自定义的根据系统环境去判断的@ConditionalOnSystemProperty。使用这个注解根据我们当前系统的某些参数决定否要注册我们的组件来。当然这种方式相对于@Enable模块实现的***ImportSelector接口更加的灵活,扩展性更高。

具体的代码可以参考GitHub:spring-boot-demo-autoconfigure小节。

原文链接:Spring Boot 条件配置@Conditional的秘密

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值