如何讓@EnableConfigurationProperties的值注入到@Value中
需求背景
定義了一個(gè)@ConfigurationProperties的配置類,然后在其中定義了一些定時(shí)任務(wù)的配置,如cron表達(dá)式,因?yàn)轫?xiàng)目會(huì)有默認(rèn)配置,遂配置中有默認(rèn)值
大體如下:
@Data
@Validated
@ConfigurationProperties(value = "task")
public class TaskConfigProperties {
/**
* 任務(wù)A在每天的0點(diǎn)5分0秒進(jìn)行執(zhí)行
*/
@NotBlank
private String taskA = "0 5 0 * * ? ";
}
定時(shí)任務(wù)配置:
@Scheduled(cron = "${task.task-a}")
public void finalCaseReportGenerate(){
log.info("taskA定時(shí)任務(wù)開始執(zhí)行");
//具體的任務(wù)
log.info("taskA定時(shí)任務(wù)完成執(zhí)行");
}但是如上直接使用是有問題的${task.taskA}是沒有值的,必須要在外部化配置中再寫一遍,這樣我們相當(dāng)于默認(rèn)值就沒有用了,這怎么行呢,我們來搞定他。
探究其原理
@ConfigurationProperties、@Value 、SpringEl 他們之間的關(guān)系和區(qū)別及我認(rèn)為的正確使用方式。
首先@ConfigurationProperties 是Spring Boot引入的,遂查詢官方文檔的講解
Spring Boot -> Externalized Configuration
我們發(fā)現(xiàn)外部化配置中沒有值的話,報(bào)錯(cuò)是在org.springframework.util.PropertyPlaceholderHelper#parseStringValue
其中org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver是解析的關(guān)鍵
我們只要把默認(rèn)值裝載到系統(tǒng)中,讓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 {
/**
* 任務(wù)A在每天的0點(diǎn)5分0秒進(jìn)行執(zhí)行
*/
@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 開始執(zhí)行");
//取到當(dāng)前配置類上的信息
MutablePropertySources propertySources = environment.getPropertySources();
Properties properties = new Properties();
if (taskA != null) {
properties.put("task.task-a", this.taskA);
}
PropertySource propertySource = new PropertiesPropertySource("task", properties);
//即優(yōu)先級(jí)低
propertySources.addLast(propertySource);
}
}需要在META-INF -> spring.factories中配置
org.springframework.boot.env.EnvironmentPostProcessor=\ cn.boommanpro.config.TaskConfigProperties

所以addLast是優(yōu)先級(jí)最低的,讓我們新加入的配置優(yōu)先級(jí)最低。
以上就簡單的完成了我們的需求。
最終實(shí)現(xiàn)
配置類中的有默認(rèn)值的不需要在External Configuration中再度配置
通過一個(gè)注解@EnableBindEnvironmentProperties,綁定含有@ConfigurationPropertiesClass的默認(rèn)值到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
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java 線程池預(yù)熱(Warm-up)實(shí)戰(zhàn)
本文主要介紹了Java 線程池預(yù)熱(Warm-up)實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-02-02
Java實(shí)現(xiàn)使用Websocket發(fā)送消息詳細(xì)代碼舉例
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)使用Websocket發(fā)送消息的相關(guān)資料,WebSocket是一種協(xié)議,用于在Web應(yīng)用程序和服務(wù)器之間建立實(shí)時(shí)、雙向的通信連接,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05
SpringBoot下實(shí)現(xiàn)session保持方式
這篇文章主要介紹了SpringBoot下實(shí)現(xiàn)session保持方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
java自帶的MessageDigest實(shí)現(xiàn)文本的md5加密算法
這篇文章主要介紹了java自帶的MessageDigest實(shí)現(xiàn)文本的md5加密算法,需要的朋友可以參考下2015-12-12
SpringBoot @FunctionalInterface注解的項(xiàng)目實(shí)戰(zhàn)
本文主要介紹了Java中的@FunctionalInterface注解及其在SpringBoot項(xiàng)目中的應(yīng)用,包括如何使用函數(shù)式接口和Lambda表達(dá)式簡化代碼,感興趣的可以了解一下2025-11-11
SpringBoot集成Open WebUI實(shí)現(xiàn)AI流式對話
本文介紹如何在 Spring Boot 項(xiàng)目中集成 Open WebUI,通過自動(dòng)管理用戶 Token、調(diào)用 OpenAI Java SDK,實(shí)現(xiàn) SSE 流式輸出,并與前端文本輸入框無縫對接,為業(yè)務(wù)系統(tǒng)注入 AI 能力,需要的朋友可以參考下2026-05-05
SpringMvc(Interceptor,Filter)實(shí)現(xiàn)方案
在JavaWeb開發(fā)中,過濾器和攔截器都是用于實(shí)現(xiàn)AOP的工具,但它們在框架層級(jí)和執(zhí)行時(shí)機(jī)上有所不同,過濾器屬于Servlet規(guī)范,位于所有Servlet之前,可以對所有請求進(jìn)行預(yù)處理,本文給大家介紹SpringMvc(Interceptor,Filter)實(shí)現(xiàn)方案,感興趣的朋友一起看看吧2026-01-01

