如何讓@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
,綁定含有@ConfigurationProperties
Class的默認(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反射機(jī)制提高SpringBoot的代碼質(zhì)量和可維護(hù)性
保持好的代碼質(zhì)量和遵守編碼標(biāo)準(zhǔn)是開發(fā)可維護(hù)和健壯軟件的重要方面,在本文中,我們將探討如何使用 Java 反射來提高 Spring Boot 應(yīng)用程序的代碼質(zhì)量和可維護(hù)性,需要的朋友可以參考下2023-10-10Java實(shí)現(xiàn)SM3withSM2簽名和驗(yàn)證的基本示例
這篇文章主要介紹了Java實(shí)現(xiàn)SM3withSM2簽名和驗(yàn)證的基本示例,SM3withSM2是一種在Java中使用的密碼學(xué)算法組合,結(jié)合了橢圓曲線公鑰密碼算法SM2和密碼哈希算法SM3,它主要用于數(shù)字簽名和數(shù)據(jù)完整性校驗(yàn),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-04-04淺析對(duì)Java關(guān)鍵字final和static的理解
本文主要給大家談?wù)勑【帉?duì)java關(guān)鍵字final和static的理解,本文給大家介紹的較詳細(xì),需要的朋友參考參考下2017-04-04Java如何取掉json數(shù)據(jù)中值為null的屬性字段
這篇文章主要介紹了Java如何取掉json數(shù)據(jù)中值為null的屬性字段,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03MyBatis處理CLOB/BLOB類型數(shù)據(jù)以及解決讀取問題
這篇文章主要介紹了MyBatis處理CLOB/BLOB類型數(shù)據(jù)以及解決讀取問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04Java Lambda表達(dá)式和函數(shù)式接口實(shí)例分析
這篇文章主要介紹了Java Lambda表達(dá)式和函數(shù)式接口,結(jié)合實(shí)例形式分析了Java8 Lambda表達(dá)式和函數(shù)式接口相關(guān)原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2019-09-09