Spring容器实例化bean条件注解@ConditionalOnProperty

demo地址:https://github.com/zackzhangCN/SpringBootDemo.git


前言

使用kafka时遇到一个问题:
项目中有两个@kafkalistener分别监听不同kafka集群, 这两个kafka集群处于不同的环境, 需要针对不同的环境, 启动不同的@kafkalistener.
一套代码在不同的条件实例化不同的实例

解决方案

Spring4提供了 @Conditional 等一系列注解, 用于在Spring容器注入bean时的条件判断.
本次使用 @ConditionalOnProperty 解决.


@ConditionalOnProperty注解

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

	// 数组,获取对应property名称的值,与name不可同时使用
	String[] value() default {};

	// 配置属性名称的前缀,比如spring.http.encoding
	String prefix() default "";

	// 数组,配置属性完整名称或部分名称
	// 可与prefix组合使用,组成完整的配置属性名称,与value不可同时使用
	String[] name() default {};

	// 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
	String havingValue() default "";

	// 缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效
	boolean matchIfMissing() default false;
}

(一) 创建maven项目

项目结构
image.png

(二) 编辑pom文件, 引入maven依赖
	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
(三) 编写application配置文件

其中一个为application.yml配置文件, 另两个为dev和pro的spring profile
内容为空即可

(四) 编写InitPropertyConfig配置文件
package cn.zack.config;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.MapPropertySource;

import java.util.HashMap;

/**
 * 实现ApplicationContextInitializer接口, 重写其initialize方法
 * 此处在SpringBoot启动时根据选择的profile, 选择注入Service1或者Service2
 *
 * @Order(Integer.MIN_VALUE) 值越小, 在容器中bean的执行顺序越靠前
 */
@Order(Integer.MIN_VALUE)
public class InitPropertyConfig implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        // 当前启动选择的spring profile
        String[] activeProfiles = configurableApplicationContext.getEnvironment().getActiveProfiles();
        String thisActiveProfile = activeProfiles[0];
        System.out.println("当前profile为: " + thisActiveProfile);

        HashMap<String, Object> map = new HashMap<>(8);
        // 当profile为dev时, 只注入service1, 当profile为pro时, 只注入service2
        if (thisActiveProfile.equals("dev")) {
            map.put("flag", "service1");
        } else if (thisActiveProfile.equals("pro")) {
            map.put("flag", "service2");
        } else {
            map.put("flag", "");
        }
        MapPropertySource propertySource = new MapPropertySource("myFlag", map);
        configurableApplicationContext.getEnvironment().getPropertySources().addLast(propertySource);
    }
}

(五) 编写启动类
package cn.zack;

import cn.zack.config.InitPropertyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class InitPropertyApplication {
    public static void main(String[] args) {
        // 创建一个springApplication, 在启动时加载自定义配置, 控制注入条件
        SpringApplication application = new SpringApplication(InitPropertyApplication.class);
        application.addInitializers(new InitPropertyConfig());
        application.run(args);
    }
}
(六) 编写两个service类
package cn.zack.service;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

/**
 * 当ApplicationContext中存在 flag的值为1时 配置生效, 注入这个bean
 */
@Component
@ConditionalOnProperty(name = "flag",havingValue = "service1")
public class Service1 {

    {
        System.out.println("注入了service1");
    }
    
    // 省略实际业务逻辑
}

package cn.zack.service;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

/**
 * 当ApplicationContext中存在 flag的值为2时 配置生效, 注入这个bean
 */
@Component
@ConditionalOnProperty(name = "flag",havingValue = "service2")
public class Service2 {

    {
        System.out.println("注入了service2");
    }
}
(七) 使用不同的spring profile启动

使用dev启动时, 控制台可以看到两行日志:

当前profile为: dev
注入了service1

使用pro启动时, 控制台可以看到:

当前profile为: pro
注入了service2

注: 当不指定profile时, 两个bean都不会被实例化.
(本次InitPropertyConfig类中未做active profile判空, 不指定profile启动时会抛异常)