1. 程式人生 > 實用技巧 >讓@EnableConfigurationProperties的值注入到@Value中

讓@EnableConfigurationProperties的值注入到@Value中

轉自:https://boommanpro.blog.csdn.net/article/details/104667530

需求背景
定義了一個@ConfigurationProperties的配置類,然後在其中定義了一些定時任務的配置,如cron表示式,因為專案會有預設配置,遂配置中有預設值,大體如下:

@Data
@Validated
@ConfigurationProperties(value = "task")
public class TaskConfigProperties {
/**
* 任務A在每天的0點5分0秒進行執行
*/
@NotBlank
private String taskA = "0 5 0 * * ? ";

}

定時任務配置:

@Scheduled(cron = "${task.task-a}")
public void finalCaseReportGenerate(){
    log.info("taskA定時任務開始執行");
    //具體的任務
    log.info("taskA定時任務完成執行");
}

但是如上直接使用是有問題的${task.taskA}是沒有值的,必須要在外部化配置中再寫一遍,這樣我們相當於預設值就沒有用了,這怎麼行呢,我們來搞定他。

探究其原理
@ConfigurationProperties、@Value 、SpringEl 他們之間的關係和區別及我認為的正確使用方式。

首先@ConfigurationProperties 是Spring Boot引入的,遂查詢官方文件的講解

Spring Boot -> Externalized Configuration

我們發現外部化配置中沒有值的話,報錯是在
org.springframework.util.PropertyPlaceholderHelper#parseStringValue
其中org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver是解析的關鍵
我們只要把預設值裝載到系統中,讓org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver#resolvePlaceholder可以解析到就可以了
遂我們可以把
值裝載到Environment中
/**

  • @author wangqimeng

  • @date 2020/3/4 0:04
    */
    @Data
    @Slf4j
    @Validated
    @ConfigurationProperties(prefix = "task")
    public class TaskConfigProperties implements InitializingBean , EnvironmentPostProcessor {

    /**

    • 任務A在每天的0點5分0秒進行執行
      */
      @NotBlank
      private String taskA = "0 5 0 * * ? ";

    @Value("${task.task-a}")
    public String taskAValue;

    @Autowired
    private Environment environment;

    @Override
    public void afterPropertiesSet() throws Exception {
    log.info("taskAValue:{}",taskAValue);
    }

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    log.info("TaskConfigProperties-> postProcessEnvironment 開始執行");
    //取到當前配置類上的資訊
    MutablePropertySources propertySources = environment.getPropertySources();
    Properties properties = new Properties();
    if (taskA != null) {
    properties.put("task.task-a", this.taskA);
    }
    PropertySource propertySource = new PropertiesPropertySource("task", properties);
    //即優先順序低
    propertySources.addLast(propertySource);
    }
    }

需要在META-INF -> spring.factories中配置

org.springframework.boot.env.EnvironmentPostProcessor=
cn.boommanpro.config.TaskConfigProperties

所以addLast是優先順序最低的,讓我們新加入的配置優先順序最低。

以上就簡單的完成了我們的需求。

最終實現
配置類中的有預設值的不需要在External Configuration中再度配置
通過一個註解@EnableBindEnvironmentProperties,繫結含有@ConfigurationPropertiesClass的預設值到Environment
@EnableBindEnvironmentProperties

/**

  • @author wangqimeng

  • @date 2020/3/4 1:21
    */
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface EnableBindEnvironmentProperties {

    Class<?>[] value() default {};
    }

@EnableBindEnvironmentPropertiesRegister

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;

/**

  • @author wangqimeng

  • @date 2020/3/4 15:11
    */
    @Slf4j
    public class EnableBindEnvironmentPropertiesRegister implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    MutablePropertySources propertySources = environment.getPropertySources();
    EnableBindEnvironmentProperties annotation = application.getMainApplicationClass().getAnnotation(EnableBindEnvironmentProperties.class);
    Arrays.stream(annotation.value())
    .forEach(aClass -> registerToEnvironment(propertySources, aClass));
    }

    public void registerToEnvironment(MutablePropertySources propertySources, Class<?> clazz) {
    ConfigurationProperties annotation = clazz.getAnnotation(ConfigurationProperties.class);
    if (annotation == null) {
    return;
    }
    String prefix = annotation.prefix();
    String name = String.format("%s-%s", prefix, clazz.getName());
    try {
    Properties properties = toProperties(prefix, clazz.newInstance());
    PropertySource propertySource = new PropertiesPropertySource(name, properties);
    propertySources.addLast(propertySource);
    } catch (Exception e) {
    log.error("Exception:", e);
    throw new RuntimeException();
    }

    }

    public Properties toProperties(String prefix, Object o) throws Exception {
    Properties properties = new Properties();
    Map<String, Object> map = objectToMap(o);
    map.forEach((s, o1) -> {
    properties.put(String.format("%s.%s", prefix, camelToUnderline(s)), o1);
    });

     return properties;
    

    }

    public static String camelToUnderline(String param) {
    if (param == null || "".equals(param.trim())) {
    return "";
    }
    int len = param.length();
    StringBuilder sb = new StringBuilder(len);
    for (int i = 0; i < len; i++) {
    char c = param.charAt(i);
    if (Character.isUpperCase(c)) {
    sb.append("-");
    sb.append(Character.toLowerCase(c));
    } else {
    sb.append(c);
    }
    }
    return sb.toString();
    }

    public static Map<String, Object> objectToMap(Object obj) throws Exception {
    if (obj == null) {
    return null;
    }
    Map<String, Object> map = new HashMap<>(10);
    BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
    for (PropertyDescriptor property : propertyDescriptors) {
    String key = property.getName();
    if (key.compareToIgnoreCase("class") == 0) {
    continue;
    }
    Method getter = property.getReadMethod();
    Object value = getter != null ? getter.invoke(obj) : null;
    if (value == null) {
    continue;
    }
    map.put(key, value);
    }

     return map;
    

    }
    }

配置到META-INF/spring.factories

Application Listeners

org.springframework.boot.env.EnvironmentPostProcessor=
cn.boommanpro.annotation.EnableBindEnvironmentPropertiesRegister