Spring Boot 鉤子全集實(shí)戰(zhàn)EnvironmentPostProcessor全解
Spring Boot 鉤子全集實(shí)戰(zhàn)(三):EnvironmentPostProcessor詳解
在上一篇中,我們聚焦了 Spring Boot 啟動(dòng)最早的擴(kuò)展點(diǎn) SpringApplicationRunListener.starting(),解決了啟動(dòng)監(jiān)控、失敗告警等核心問題。今天,我們將深入講解配置加載階段的核心擴(kuò)展點(diǎn)——EnvironmentPostProcessor,它是定制化配置加載、動(dòng)態(tài)配置注入、配置加密解密的 “黃金入口”,也是生產(chǎn)環(huán)境中配置治理的核心工具。
一、什么是EnvironmentPostProcessor?
EnvironmentPostProcessor 是 Spring Boot 提供的配置后置處理擴(kuò)展點(diǎn),在以下時(shí)機(jī)被觸發(fā):
- Environment 已初始化(但未完全加載所有配置源);
- 應(yīng)用配置文件(application.yml/properties)已讀取,但未生效;
- ApplicationContext 尚未創(chuàng)建,但可修改 Environment 中的配置;
- 執(zhí)行優(yōu)先級(jí)高于
@Configuration、@Value等配置注入邏輯。
? 核心價(jià)值:在配置最終生效前,對(duì)配置進(jìn)行動(dòng)態(tài)修改、補(bǔ)充、加密解密,實(shí)現(xiàn)配置的統(tǒng)一治理。
生產(chǎn)環(huán)境中,這個(gè)擴(kuò)展點(diǎn)常用于解決 “配置中心化”“配置加密”“多環(huán)境配置動(dòng)態(tài)切換” 等核心問題。
二、場景 1:配置中心拉?。ㄌ娲渲梦募?/h3>
業(yè)務(wù)痛點(diǎn)
生產(chǎn)環(huán)境中,若將配置寫死在 application.yml 中,存在以下問題:
- 配置與代碼耦合,修改配置需重新打包部署;
- 多環(huán)境(dev/test/prod)配置管理混亂,易出錯(cuò);
- 配置缺乏統(tǒng)一管控,泄露風(fēng)險(xiǎn)高。
解決方案
基于 EnvironmentPostProcessor 從配置中心(如 Nacos/Apollo/ 攜程 Apollo)拉取配置,覆蓋本地配置,實(shí)現(xiàn)配置與代碼解耦。
實(shí)現(xiàn)代碼
package com.example.demo.envprocessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import java.util.HashMap;
import java.util.Map;
/**
* 生產(chǎn)級(jí)配置中心拉取處理器
*/
public class ConfigCenterEnvironmentPostProcessor implements EnvironmentPostProcessor {
// 模擬配置中心客戶端(生產(chǎn)環(huán)境替換為真實(shí)Nacos/Apollo客戶端)
private static class ConfigCenterClient {
// 根據(jù)環(huán)境拉取配置
public Map<String, Object> pullConfig(String env) {
Map<String, Object> configMap = new HashMap<>();
// 生產(chǎn)環(huán)境從配置中心拉取真實(shí)配置
switch (env) {
case "prod":
configMap.put("spring.datasource.url", "jdbc:mysql://prod-mysql:3306/prod_db?useSSL=false");
configMap.put("spring.datasource.username", "prod_user");
configMap.put("spring.datasource.password", "prod_pass123");
configMap.put("redis.host", "prod-redis:6379");
configMap.put("app.prod.mode", "true");
break;
case "test":
configMap.put("spring.datasource.url", "jdbc:mysql://test-mysql:3306/test_db?useSSL=false");
configMap.put("spring.datasource.username", "test_user");
configMap.put("spring.datasource.password", "test_pass123");
configMap.put("redis.host", "test-redis:6379");
configMap.put("app.prod.mode", "false");
break;
default:
configMap.put("spring.datasource.url", "jdbc:mysql://localhost:3306/dev_db?useSSL=false");
configMap.put("spring.datasource.username", "root");
configMap.put("spring.datasource.password", "root");
configMap.put("redis.host", "localhost:6379");
configMap.put("app.prod.mode", "false");
}
return configMap;
}
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 1. 獲取當(dāng)前激活的環(huán)境(通過啟動(dòng)參數(shù)/系統(tǒng)變量傳遞)
String[] activeProfiles = environment.getActiveProfiles();
String env = activeProfiles.length > 0 ? activeProfiles[0] : "dev";
System.out.printf("[配置中心] 開始拉取 %s 環(huán)境配置%n", env);
// 2. 從配置中心拉取配置
ConfigCenterClient client = new ConfigCenterClient();
Map<String, Object> configMap = client.pullConfig(env);
// 3. 將配置注入Environment(優(yōu)先級(jí)高于本地配置)
MutablePropertySources propertySources = environment.getPropertySources();
// 添加到最前面,確保優(yōu)先級(jí)最高
propertySources.addFirst(new MapPropertySource("configCenterProperties", configMap));
// 4. 打印加載結(jié)果(生產(chǎn)環(huán)境建議用SLF4J)
System.out.printf("[配置中心] 成功加載 %d 個(gè)配置項(xiàng),環(huán)境:%s%n", configMap.size(), env);
configMap.forEach((key, value) -> {
// 密碼脫敏輸出
String displayValue = key.contains("password") ? "******" : String.valueOf(value);
System.out.printf("[配置中心] %s = %s%n", key, displayValue);
});
}
}配置加載
在 resources/META-INF/spring.factories 中配置:
org.springframework.boot.env.EnvironmentPostProcessor=\ com.example.demo.envprocessor.ConfigCenterEnvironmentPostProcessor
啟動(dòng)測試
添加啟動(dòng)參數(shù)激活生產(chǎn)環(huán)境:--spring.profiles.active=prod
輸出
[配置中心] 開始拉取 prod 環(huán)境配置 [配置中心] 成功加載 5 個(gè)配置項(xiàng),環(huán)境:prod [配置中心] spring.datasource.username = prod_user [配置中心] spring.datasource.url = jdbc:mysql://prod-mysql:3306/prod_db?useSSL=false [配置中心] redis.host = prod-redis:6379 [配置中心] app.prod.mode = true [配置中心] spring.datasource.password = ****** . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.5.8) 2025-12-11T21:46:23.284+08:00 INFO 10075 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 21.0.9 with PID 10075 (/Users/wangmingfei/Documents/個(gè)人/05 java天梯之路/01 源碼/03 每日打卡系列/daily-check-in/springboot鉤子/demo/target/classes started by wangmingfei in /Users/wangmingfei/Documents/個(gè)人/05 java天梯之路/01 源碼/03 每日打卡系列/daily-check-in/springboot鉤子/demo) 2025-12-11T21:46:23.288+08:00 INFO 10075 --- [ main] com.example.demo.DemoApplication : The following 1 profile is active: "prod" 2025-12-11T21:46:23.575+08:00 INFO 10075 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2025-12-11T21:46:23.582+08:00 INFO 10075 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2025-12-11T21:46:23.582+08:00 INFO 10075 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.49] 2025-12-11T21:46:23.600+08:00 INFO 10075 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2025-12-11T21:46:23.600+08:00 INFO 10075 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 298 ms 2025-12-11T21:46:23.739+08:00 INFO 10075 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2025-12-11T21:46:23.743+08:00 INFO 10075 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 9.056 seconds (process running for 9.206)
生產(chǎn)價(jià)值
- 配置與代碼解耦,修改配置無需重新部署;
- 多環(huán)境配置統(tǒng)一管控,避免配置混亂;
- 配置中心可實(shí)現(xiàn)配置熱更新、灰度發(fā)布等高級(jí)特性;
- 配置拉取過程可增加鑒權(quán)、加密,提升安全性。
三、場景 2:配置加密解密(敏感配置防泄露)
業(yè)務(wù)痛點(diǎn)
生產(chǎn)環(huán)境中,數(shù)據(jù)庫密碼、Redis 密碼、接口密鑰等敏感配置若明文存儲(chǔ),存在嚴(yán)重安全風(fēng)險(xiǎn):
- 配置文件泄露導(dǎo)致敏感信息被盜;
- 運(yùn)維人員可直接查看明文密碼,不符合安全規(guī)范;
- 審計(jì)無法追溯密碼使用記錄。
解決方案
基于 EnvironmentPostProcessor 對(duì)加密的配置進(jìn)行解密,敏感配置在配置文件中以密文存儲(chǔ),運(yùn)行時(shí)動(dòng)態(tài)解密。
實(shí)現(xiàn)代碼
package com.example.demo.envprocessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/** * 敏感配置解密處理器 */
public class EncryptedConfigEnvironmentPostProcessor implements EnvironmentPostProcessor {
// 加密密鑰(生產(chǎn)環(huán)境從安全存儲(chǔ)中讀取,如KMS/本地加密文件)
private static final String ENCRYPT_KEY = "prod_key_1234567"; // 實(shí)際使用需16/24/32位
// 密文前綴標(biāo)識(shí)
private static final String ENCRYPT_PREFIX = "encrypt:";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
System.out.println("[配置解密] 開始處理敏感配置解密");
// 遍歷所有配置源,解密敏感配置
for (PropertySource<?> propertySource : environment.getPropertySources()) {
// 跳過系統(tǒng)配置源,只處理應(yīng)用配置
if (propertySource.getName().startsWith("system") || propertySource.getName().startsWith("configCenter")) {
continue;
}
// 解密核心敏感配置
decryptConfig(environment, "spring.datasource.password");
decryptConfig(environment, "redis.password");
decryptConfig(environment, "app.api.secret");
}
System.out.println("[配置解密] 敏感配置解密完成");
}
// 解密單個(gè)配置項(xiàng)
private void decryptConfig(ConfigurableEnvironment environment, String configKey) {
String value = environment.getProperty(configKey);
if (StringUtils.hasText(value) && value.startsWith(ENCRYPT_PREFIX)) {
try {
// 截取密文部分
String cipherText = value.substring(ENCRYPT_PREFIX.length());
// 解密
String plainText = decrypt(cipherText, ENCRYPT_KEY);
// 替換為明文(注入到最高優(yōu)先級(jí)配置源)
environment.getSystemProperties().put(configKey, plainText);
System.out.printf("[配置解密] 成功解密配置項(xiàng):%s%n", configKey);
} catch (Exception e) {
throw new RuntimeException("配置解密失?。? + configKey, e);
}
}
}
// AES解密實(shí)現(xiàn)(生產(chǎn)環(huán)境建議使用非對(duì)稱加密RSA)
private String decrypt(String cipherText, String key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(cipherText));
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
// 加密方法(用于生成密文配置)
public static String encrypt(String plainText, String key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// 測試生成密文
public static void main(String[] args) throws Exception {
// 生成密文:encrypt:xxxxxx
String password = "prod_pass123";
String cipherText = encrypt(password, ENCRYPT_KEY);
System.out.println("密文配置:encrypt:" + cipherText);
}
}配置加載
在 resources/META-INF/spring.factories 中配置:
org.springframework.boot.env.EnvironmentPostProcessor=\ com.example.demo.envprocessor.EncryptedConfigEnvironmentPostProcessor
配置文件(application.yml)
spring:
datasource:
url: jdbc:mysql://prod-mysql:3306/prod_db?useSSL=false
username: prod_user
# 密文存儲(chǔ),前綴標(biāo)識(shí)需要解密
password: encrypt:DC76b3+IyNwp+f/1QxPiIA==
redis:
host: prod-redis:6379
password: encrypt:DC76b3+IyNwp+f/1QxPiIA==
app:
api:
secret: encrypt:DC76b3+IyNwp+f/1QxPiIA==輸出
[配置解密] 開始處理敏感配置解密
[配置解密] 成功解密配置項(xiàng):spring.datasource.password
[配置解密] 成功解密配置項(xiàng):redis.password
[配置解密] 成功解密配置項(xiàng):app.api.secret
[配置解密] 敏感配置解密完成
生產(chǎn)價(jià)值
- 敏感配置密文存儲(chǔ),即使配置文件泄露也無法獲取明文;
- 解密邏輯集中管控,符合等保合規(guī)要求;
- 可結(jié)合 KMS(密鑰管理服務(wù))實(shí)現(xiàn)密鑰的安全存儲(chǔ),避免硬編碼;
- 解密過程可增加審計(jì)日志,追溯敏感配置使用記錄。
四、場景 3:多環(huán)境配置動(dòng)態(tài)覆蓋(解決配置沖突)
業(yè)務(wù)痛點(diǎn)
生產(chǎn)環(huán)境中,多環(huán)境配置常出現(xiàn)以下問題:
- 測試環(huán)境配置污染生產(chǎn)環(huán)境;
- 不同環(huán)境的配置優(yōu)先級(jí)混亂;
- 特殊場景(如灰度發(fā)布)需要臨時(shí)覆蓋配置。
解決方案
基于 EnvironmentPostProcessor 實(shí)現(xiàn)配置的動(dòng)態(tài)覆蓋,根據(jù)環(huán)境、機(jī)器標(biāo)簽等條件動(dòng)態(tài)調(diào)整配置優(yōu)先級(jí)。
實(shí)現(xiàn)代碼
package com.example.demo.envprocessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
/** * 多環(huán)境配置動(dòng)態(tài)覆蓋處理器 */
public class EnvConfigOverrideProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
try {
// 1. 獲取機(jī)器標(biāo)識(shí)(生產(chǎn)環(huán)境可從機(jī)器標(biāo)簽/ECS元數(shù)據(jù)獲?。?
String hostName = InetAddress.getLocalHost().getHostName();
String ip = InetAddress.getLocalHost().getHostAddress();
System.out.printf("[配置覆蓋] 機(jī)器信息:%s(%s)%n", hostName, ip);
// 2. 判斷是否為灰度機(jī)器
boolean isGray = hostName.contains("gray") || ip.startsWith("192.168.100.");
// 判斷是否為生產(chǎn)環(huán)境
boolean isProd = environment.getActiveProfiles().length > 0 &&
"prod".equals(environment.getActiveProfiles()[0]);
// 3. 動(dòng)態(tài)覆蓋配置
Map<String, Object> overrideConfig = new HashMap<>();
if (isProd && isGray) {
// 灰度機(jī)器使用灰度配置
overrideConfig.put("spring.datasource.url", "jdbc:mysql://gray-mysql:3306/prod_db?useSSL=false");
overrideConfig.put("redis.host", "gray-redis:6379");
overrideConfig.put("app.gray.mode", "true");
System.out.println("[配置覆蓋] 灰度機(jī)器,加載灰度配置");
} else if (isProd) {
// 生產(chǎn)機(jī)器使用生產(chǎn)配置
overrideConfig.put("app.gray.mode", "false");
overrideConfig.put("app.log.level", "INFO");
System.out.println("[配置覆蓋] 生產(chǎn)機(jī)器,加載生產(chǎn)配置");
} else {
// 測試/開發(fā)機(jī)器放寬配置限制
overrideConfig.put("app.log.level", "DEBUG");
overrideConfig.put("spring.datasource.hikari.maximum-pool-size", "10");
System.out.println("[配置覆蓋] 非生產(chǎn)機(jī)器,加載測試配置");
}
// 4. 覆蓋配置(優(yōu)先級(jí)最高)
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addFirst(new MapPropertySource("dynamicOverrideConfig", overrideConfig));
// 5. 打印最終生效的核心配置
System.out.printf("[配置覆蓋] 最終生效的數(shù)據(jù)庫地址:%s%n",
environment.getProperty("spring.datasource.url"));
System.out.printf("[配置覆蓋] 最終生效的灰度模式:%s%n",
environment.getProperty("app.gray.mode"));
} catch (UnknownHostException e) {
throw new RuntimeException("獲取機(jī)器信息失敗", e);
}
}
}配置加載
在 resources/META-INF/spring.factories 中配置:
org.springframework.boot.env.EnvironmentPostProcessor=\ com.example.demo.envprocessor.EnvConfigOverrideProcessor
輸出(生產(chǎn)機(jī)器)
[配置覆蓋] 機(jī)器信息:xxx(127.0.0.1)
[配置覆蓋] 生產(chǎn)機(jī)器,加載生產(chǎn)配置
[配置覆蓋] 最終生效的數(shù)據(jù)庫地址:jdbc:mysql://prod-mysql:3306/prod_db?useSSL=false
[配置覆蓋] 最終生效的灰度模式:false
生產(chǎn)價(jià)值
- 基于機(jī)器標(biāo)簽動(dòng)態(tài)調(diào)整配置,實(shí)現(xiàn)灰度發(fā)布、藍(lán)綠部署;
- 避免測試配置污染生產(chǎn)環(huán)境,配置隔離更徹底;
- 生產(chǎn)環(huán)境可臨時(shí)覆蓋配置,無需修改配置文件;
- 配置優(yōu)先級(jí)可精細(xì)化控制,解決配置沖突問題。
五、場景 4:配置校驗(yàn)與補(bǔ)全(提前攔截非法配置)
業(yè)務(wù)痛點(diǎn)
生產(chǎn)環(huán)境中,配置錯(cuò)誤常導(dǎo)致應(yīng)用啟動(dòng)后不可用:
- 核心配置缺失(如數(shù)據(jù)庫地址為空);
- 配置格式錯(cuò)誤(如端口號(hào)非數(shù)字);
- 配置值超出合理范圍(如線程池大小設(shè)置為 0)。
解決方案
基于 EnvironmentPostProcessor 在配置生效前進(jìn)行校驗(yàn),非法配置直接終止啟動(dòng),并給出明確的錯(cuò)誤提示。
實(shí)現(xiàn)代碼
package com.example.demo.envprocessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.StringUtils;
import java.util.regex.Pattern;
/** * 配置校驗(yàn)與補(bǔ)全處理器 */
public class ConfigValidateProcessor implements EnvironmentPostProcessor {
// 端口號(hào)正則
private static final Pattern PORT_PATTERN = Pattern.compile("^\\d{1,5}$");
// 數(shù)據(jù)庫URL正則
private static final Pattern DB_URL_PATTERN = Pattern.compile("^jdbc:\\w+://.+:\\d+/\\w+.*$");
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
System.out.println("[配置校驗(yàn)] 開始校驗(yàn)核心配置");
// 1. 校驗(yàn)核心配置是否存在
validateConfigExists(environment, "spring.datasource.url");
validateConfigExists(environment, "spring.datasource.username");
validateConfigExists(environment, "spring.datasource.password");
validateConfigExists(environment, "redis.host");
// 2. 校驗(yàn)配置格式
validateConfigFormat(environment, "spring.datasource.url", DB_URL_PATTERN, "數(shù)據(jù)庫URL格式非法");
validateConfigFormat(environment, "server.port", PORT_PATTERN, "端口號(hào)格式非法");
// 3. 校驗(yàn)配置值范圍
validatePortRange(environment, "server.port");
validateThreadPoolSize(environment, "spring.datasource.hikari.maximum-pool-size");
// 4. 補(bǔ)全默認(rèn)配置
supplementDefaultConfig(environment, "server.port", "8080");
supplementDefaultConfig(environment, "spring.datasource.hikari.minimum-idle", "5");
System.out.println("[配置校驗(yàn)] 核心配置校驗(yàn)通過,默認(rèn)配置已補(bǔ)全");
}
// 校驗(yàn)配置是否存在
private void validateConfigExists(ConfigurableEnvironment environment, String configKey) {
String value = environment.getProperty(configKey);
if (!StringUtils.hasText(value)) {
throw new IllegalArgumentException("核心配置缺失:" + configKey);
}
}
// 校驗(yàn)配置格式
private void validateConfigFormat(ConfigurableEnvironment environment, String configKey,
Pattern pattern, String errorMsg) {
String value = environment.getProperty(configKey);
if (StringUtils.hasText(value) && !pattern.matcher(value).matches()) {
throw new IllegalArgumentException(errorMsg + ",配置項(xiàng):" + configKey + ",值:" + value);
}
}
// 校驗(yàn)端口號(hào)范圍
private void validatePortRange(ConfigurableEnvironment environment, String configKey) {
String value = environment.getProperty(configKey);
if (StringUtils.hasText(value) && PORT_PATTERN.matcher(value).matches()) {
int port = Integer.parseInt(value);
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("端口號(hào)超出范圍(1-65535):" + configKey + "=" + value);
}
}
}
// 校驗(yàn)線程池大小
private void validateThreadPoolSize(ConfigurableEnvironment environment, String configKey) {
String value = environment.getProperty(configKey);
if (StringUtils.hasText(value)) {
try {
int size = Integer.parseInt(value);
if (size < 1 || size > 100) {
throw new IllegalArgumentException("線程池大小超出合理范圍(1-100):" + configKey + "=" + value);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("線程池大小必須為數(shù)字:" + configKey + "=" + value);
}
}
}
// 補(bǔ)全默認(rèn)配置
private void supplementDefaultConfig(ConfigurableEnvironment environment, String configKey, String defaultValue) {
String value = environment.getProperty(configKey);
if (!StringUtils.hasText(value)) {
environment.getSystemProperties().put(configKey, defaultValue);
System.out.printf("[配置補(bǔ)全] 配置項(xiàng) %s 缺失,使用默認(rèn)值:%s%n", configKey, defaultValue);
}
}
}配置加載
在 resources/META-INF/spring.factories 中配置:
org.springframework.boot.env.EnvironmentPostProcessor=\ com.example.demo.envprocessor.ConfigValidateProcessor
配置文件
#spring:
# datasource:
# url: jdbc:mysql://prod-mysql:3306/prod_db?useSSL=false
# username: prod_user
# # 密文存儲(chǔ),前綴標(biāo)識(shí)需要解密
# password: encrypt:DC76b3+IyNwp+f/1QxPiIA==
redis:
host: prod-redis:6379
password: encrypt:DC76b3+IyNwp+f/1QxPiIA==
app:
api:
secret: encrypt:DC76b3+IyNwp+f/1QxPiIA==錯(cuò)誤輸出示例
[配置校驗(yàn)] 開始校驗(yàn)核心配置
22:15:56.187 [main] ERROR org.springframework.boot.SpringApplication -- Application run failed
java.lang.IllegalArgumentException: 核心配置缺失:spring.datasource.url
at com.example.demo.envprocessor.ConfigValidateProcessor.validateConfigExists(ConfigValidateProcessor.java:47)
at com.example.demo.envprocessor.ConfigValidateProcessor.postProcessEnvironment(ConfigValidateProcessor.java:23)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:132)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:115)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138)
at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81)
at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:353)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:313)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350)
at com.example.demo.DemoApplication.main(DemoApplication.java:11)
已與地址為 ''127.0.0.1:57589',傳輸: '套接字'' 的目標(biāo)虛擬機(jī)斷開連接生產(chǎn)價(jià)值
- 提前攔截非法配置,避免應(yīng)用啟動(dòng)后不可用;
- 錯(cuò)誤提示明確,縮短配置問題排查時(shí)間;
- 補(bǔ)全默認(rèn)配置,減少配置文件維護(hù)成本;
- 統(tǒng)一校驗(yàn)規(guī)則,符合生產(chǎn)環(huán)境配置規(guī)范。
六、總結(jié)
EnvironmentPostProcessor 是 Spring Boot 配置治理的核心擴(kuò)展點(diǎn),它在配置最終生效前提供了強(qiáng)大的定制化能力:
- 配置中心化:從配置中心拉取配置,解耦配置與代碼;
- 配置加密:敏感配置密文存儲(chǔ),運(yùn)行時(shí)動(dòng)態(tài)解密;
- 動(dòng)態(tài)覆蓋:基于環(huán)境 / 機(jī)器標(biāo)簽動(dòng)態(tài)調(diào)整配置;
- 配置校驗(yàn):提前攔截非法配置,保障應(yīng)用啟動(dòng)成功。
相較于 SpringApplicationRunListener,EnvironmentPostProcessor 更聚焦于配置層面的擴(kuò)展,是構(gòu)建 “配置即代碼”“配置統(tǒng)一管控” 生產(chǎn)級(jí)應(yīng)用的關(guān)鍵工具。
到此這篇關(guān)于Spring Boot 鉤子全集實(shí)戰(zhàn)EnvironmentPostProcessor全解的文章就介紹到這了,更多相關(guān)Spring Boot EnvironmentPostProcessor內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 后端接收Request請(qǐng)求參數(shù)的多種方式匯總
本文給大家總結(jié)了后端接收Request請(qǐng)求參數(shù)的7種方式,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-10-10
SpringCloud Alibaba Nacos 整合SpringBoot A
這篇文章主要介紹了SpringCloud Alibaba Nacos 整合SpringBoot Admin實(shí)戰(zhàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java如何將json字符串與實(shí)體類互相轉(zhuǎn)換
在我們調(diào)用三方平臺(tái)接口時(shí),經(jīng)常需要將我們封裝的實(shí)體類轉(zhuǎn)換為json作為傳參,下面這篇文章主要給大家介紹了關(guān)于Java如何將json字符串與實(shí)體類互相轉(zhuǎn)換的相關(guān)資料,需要的朋友可以參考下2023-11-11
feign遠(yuǎn)程調(diào)用無法傳遞對(duì)象屬性405的問題
這篇文章主要介紹了feign遠(yuǎn)程調(diào)用無法傳遞對(duì)象屬性405的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java中forward轉(zhuǎn)發(fā)與redirect重定向的區(qū)別
轉(zhuǎn)發(fā)和重定向都是常用的頁面跳轉(zhuǎn)方式,但在實(shí)現(xiàn)上有一些區(qū)別,本文主要介紹了Java中forward轉(zhuǎn)發(fā)與redirect重定向的區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11
MyBatis配置數(shù)據(jù)庫連接并實(shí)現(xiàn)交互的操作步驟
這篇文章主要介紹了MyBatis配置數(shù)據(jù)庫連接并實(shí)現(xiàn)交互的操作步驟,本文給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-04-04

