文章目录
- 概述
- EnvironmentPostProcessor 作用
- EnvironmentPostProcessor 实现和注册
- 创建类并实现接口
- 注册到 Spring Boot
- 常见应用场景
- 源码分析
- 1. `EnvironmentPostProcessor` 接口定义
- 2. 扩展点加载流程
- 3. 加载 `EnvironmentPostProcessor` 实现类
- 4. `EnvironmentPostProcessor` 执行时机
- 5. 环境属性源的扩展示例
- 6. `EnvironmentPostProcessor` 应用场景
- 真实案例
- 小结
概述
EnvironmentPostProcessor 是 Spring Boot 提供的一个扩展点,用于在应用环境初始化过程中执行一些额外的处理。该接口允许开发者在 Spring Environment
初始化完成后、应用上下文加载之前,自定义和调整环境变量,这为配置和条件化应用设置提供了极大的灵活性。
EnvironmentPostProcessor 作用
EnvironmentPostProcessor
作为一个接口,允许在 Spring Environment
对象被创建和配置后进行扩展操作。通过它,可以在应用启动时添加、修改或删除 Environment
中的属性值,比如注入额外的配置源、动态设置配置项、调整日志级别等。
使用场景:
- 自定义配置源,例如从数据库或远程服务动态加载配置。
- 修改现有的配置值,或基于环境动态调整配置。
- 执行一些特定逻辑,如根据某些条件禁用部分功能或切换日志级别。
EnvironmentPostProcessor 实现和注册
创建类并实现接口
实现 EnvironmentPostProcessor
接口的类,需要重写其 postProcessEnvironment
方法,在该方法中注入或修改环境配置。
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Map<String, Object> customProperties = new HashMap<>();
customProperties.put("custom.property", "customValue");
environment.getPropertySources().addFirst(new MapPropertySource("customProperties", customProperties));
}
}
注册到 Spring Boot
将 CustomEnvironmentPostProcessor
注册为一个 EnvironmentPostProcessor
,通过在 META-INF/spring.factories
文件中添加如下配置:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.CustomEnvironmentPostProcessor
Spring Boot 会自动扫描 spring.factories
中的定义,并在环境初始化时加载 CustomEnvironmentPostProcessor
。
常见应用场景
- 动态配置加载:可在启动时通过读取外部配置源(如数据库、远程服务)来加载配置。
- 条件化配置注入:基于环境条件,动态插入某些配置项。
- 日志配置:在应用启动时根据外部条件设置日志级别或日志输出位置。
- 多环境支持:可以根据当前环境(如 dev、prod)注入不同的默认配置。
源码分析
1. EnvironmentPostProcessor
接口定义
EnvironmentPostProcessor
是一个非常简单的接口,仅包含一个方法 postProcessEnvironment
。下面是其接口的定义:
package org.springframework.boot.env;
import org.springframework.boot.SpringApplication;
import org.springframework.core.env.ConfigurableEnvironment;
@FunctionalInterface
public interface EnvironmentPostProcessor {
void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}
-
参数解释:
ConfigurableEnvironment environment
:当前 Spring 应用的Environment
对象,通过该参数可以访问和操作环境中的属性源。SpringApplication application
:Spring 应用的入口,提供对应用的一些基本信息和配置信息的访问。
-
注解:
@FunctionalInterface
表明它是一个函数式接口,可以用 lambda 表达式实现。
2. 扩展点加载流程
在 Spring Boot 应用启动时,SpringApplication
类负责初始化并加载一系列的配置扩展点,EnvironmentPostProcessor
也是在此阶段被加载的。SpringApplication
会在初始化过程中,使用 SpringFactoriesLoader
从 META-INF/spring.factories
文件中加载所有注册的 EnvironmentPostProcessor
实现。
关键代码在 SpringApplication
类的 configureEnvironment
方法中:
// SpringApplication.java 中
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// 创建并配置环境
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
// 配置 Environment,包括加载 EnvironmentPostProcessor
this.configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
// 绑定环境属性并返回
return environment;
}
在 configureEnvironment
方法中,会调用 applyInitializers
方法,加载所有的 ApplicationContextInitializer
和 EnvironmentPostProcessor
实现。
3. 加载 EnvironmentPostProcessor
实现类
SpringFactoriesLoader
是 Spring 的一个通用加载工具,用于从 META-INF/spring.factories
文件中读取配置类并实例化。Spring Boot 使用 SpringFactoriesLoader
来加载 EnvironmentPostProcessor
实现类,并在应用启动时逐一执行它们的 postProcessEnvironment
方法。
// SpringApplication.java 中
private void applyInitializers(ConfigurableApplicationContext context) {
List<ApplicationContextInitializer<?>> initializers = getInitializers();
for (ApplicationContextInitializer<?> initializer : initializers) {
if (initializer instanceof EnvironmentPostProcessor) {
((EnvironmentPostProcessor) initializer).postProcessEnvironment(context.getEnvironment(), this);
}
}
}
排序执行: Spring Boot 会将所有找到的 EnvironmentPostProcessor 实现按照 @Order 注解或实现 Ordered 接口的方式进行排序,然后依次执行它们的 postProcessEnvironment 方法
4. EnvironmentPostProcessor
执行时机
EnvironmentPostProcessor
的 postProcessEnvironment
方法会在应用上下文 ApplicationContext
初始化之前被调用。也就是说,在加载应用的 @Bean
和其他上下文配置之前,EnvironmentPostProcessor
已经对环境进行了处理。
此调用顺序确保开发者可以在应用加载所有属性和资源文件之前,修改环境变量或添加新的属性源。这对动态配置源或在运行时确定属性值的场景特别有用。
修改 Environment: 在 postProcessEnvironment 方法中,可以通过 ConfigurableEnvironment 对象操作应用的环境。最常见的操作是添加自定义的 PropertySource,例如从数据库或配置文件中读取属性
5. 环境属性源的扩展示例
假设我们希望在环境中加入一个自定义配置,可以通过 EnvironmentPostProcessor
来实现:
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 创建一个自定义属性源
Map<String, Object> customProperties = new HashMap<>();
customProperties.put("custom.property", "customValue");
// 将自定义属性源添加到环境中
environment.getPropertySources().addFirst(new MapPropertySource("customProperties", customProperties));
}
}
6. EnvironmentPostProcessor
应用场景
- 动态属性注入:在应用启动时从数据库或外部服务获取配置,并将其添加到
Environment
中。 - 基于环境的配置:根据当前环境变量(如
dev
、prod
)动态设置不同的配置。 - 日志配置:通过
EnvironmentPostProcessor
可以在应用启动前修改日志级别或日志文件路径。
真实案例
多个配置文件
CustomEnvironmentPostProcessor
@Component
@Slf4j
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 从环境变量设置JasyptKey
String jasyptKey = environment.getProperty("jasypt.encryptor.key");
if (StringUtils.isNotBlank(jasyptKey)) {
JasyptUtil.setJasyptKey(jasyptKey);
log.info("set JasyptUtil jasyptKey success");
}
Map<String, Object> properties = new HashMap<>(10);
// nacos参数设置,从environment中获取属性值
String nacosUserName = environment.getProperty("spring.cloud.nacos.discovery.username");
if (StringUtils.isNotBlank(nacosUserName)) {
// 重新设置值
properties.put("spring.cloud.nacos.discovery.username", JasyptUtil.jasyptDecrypt4EndPoint(nacosUserName));
log.info("set spring.cloud.nacos.discovery.username success");
}
String nacosPassword = environment.getProperty("spring.cloud.nacos.discovery.password");
if (StringUtils.isNotBlank(nacosPassword)) {
properties.put("spring.cloud.nacos.discovery.password", JasyptUtil.jasyptDecrypt4EndPoint(nacosPassword));
log.info("set spring.cloud.nacos.discovery.password success");
}
// redis参数设置,从environment中获取属性值
String redisPwd = environment.getProperty("spring.redis.password");
if (StringUtils.isNotBlank(redisPwd)) {
// 重新设置值
properties.put("spring.redis.password", JasyptUtil.jasyptDecrypt4EndPoint(redisPwd));
log.info("set spring.redis.password success");
}
// es参数设置,从environment中获取属性值
String esUserName = environment.getProperty("spring.elasticsearch.bboss.elasticUser");
if (StringUtils.isNotBlank(esUserName)) {
// 重新设置值
properties.put("spring.elasticsearch.bboss.elasticUser", JasyptUtil.jasyptDecrypt4EndPoint(esUserName));
log.info("set spring.elasticsearch.bboss.elasticUser success");
}
String esPwd = environment.getProperty("spring.elasticsearch.bboss.elasticPassword");
if (StringUtils.isNotBlank(esPwd)) {
// 重新设置值
properties.put("spring.elasticsearch.bboss.elasticPassword", JasyptUtil.jasyptDecrypt4EndPoint(esPwd));
log.info("set spring.elasticsearch.bboss.elasticPassword success");
}
//Kafka 参数设置
String kafkaTurstStorePassword = environment.getProperty("spring.kafka.ssl.trust-store-password");
if (StringUtils.isNotBlank(kafkaTurstStorePassword)) {
// 重新设置值
properties.put("spring.kafka.ssl.trust-store-password", JasyptUtil.jasyptDecrypt4EndPoint(kafkaTurstStorePassword));
log.info("set spring.kafka.ssl.trust-store-password success");
}
String kafkaJaasConfig = environment.getProperty("spring.kafka.properties.sasl.jaas.config");
if (StringUtils.isNotBlank(kafkaJaasConfig)) {
// 重新设置值
properties.put("spring.kafka.properties.sasl.jaas.config", JasyptUtil.jasyptDecrypt4EndPoint(kafkaJaasConfig));
log.info("set spring.kafka.properties.sasl.jaas.config success");
}
// 加载到环境变量中
environment.getPropertySources().addFirst(new MapPropertySource("customProperties", properties));
//Windows系统暂时移除关于kafka的认证
if (System.getProperty("os.name").toLowerCase().contains("win")) {
environment.getPropertySources().remove("spring.kafka.ssl.trust-store-location");
environment.getPropertySources().remove("spring.kafka.ssl.trust-store-password");
environment.getPropertySources().remove("spring.kafka.security.protocol");
environment.getPropertySources().remove("spring.kafka.properties.sasl.mechanism");
environment.getPropertySources().remove("spring.kafka.properties.sasl.jaas.config");
environment.getPropertySources().remove("spring.kafka.properties.ssl.endpoint.identification.algorithm");
}
// monitor
String monitorUserName = environment.getProperty("ko-time.user-name");
if (StringUtils.isNotBlank(monitorUserName)) {
// 重新设置值
properties.put("ko-time.user-name", JasyptUtil.jasyptDecrypt4EndPoint(monitorUserName));
log.info("set ko-time.user-name success");
}
String monitorPwd = environment.getProperty("ko-time.password");
if (StringUtils.isNotBlank(monitorPwd)) {
// 重新设置值
properties.put("ko-time.password", JasyptUtil.jasyptDecrypt4EndPoint(monitorPwd));
log.info("set ko-time.password success");
}
}
}
import lombok.extern.slf4j.Slf4j;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.util.password.BasicPasswordEncryptor;
import org.jasypt.util.text.BasicTextEncryptor;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
* <p>
*/
@Slf4j
public class JasyptUtil {
/**
* JasyptKey
*/
private static String jasyptKey = "xxxxxxxxxxxxx";
// 创建BasicTextEncryptor实例用于密码加密和解密
private static final BasicTextEncryptor basicTextEncryptor = new BasicTextEncryptor();
// 创建BasicPasswordEncryptor实例用于密码加密和验证
private static final BasicPasswordEncryptor basicPasswordEncryptor = new BasicPasswordEncryptor();
// 创建PooledPBEStringEncryptor并配置加密器,使用池化技术以支持多线程加密
private static final PooledPBEStringEncryptor pooledPBEStringEncryptor = new PooledPBEStringEncryptor();
public static void setJasyptKey(String key) {
JasyptUtil.jasyptKey = key;
}
static {
//基础加解密实例参数初始化
basicTextEncryptor.setPassword(jasyptKey);
//多线程加解密实例参数初始化
// 设置线程池大小为6,即同时最多有6个线程执行加密操作
pooledPBEStringEncryptor.setPoolSize(6);
// 设置加密使用的密码
pooledPBEStringEncryptor.setPassword(jasyptKey);
// 设置加密算法
pooledPBEStringEncryptor.setAlgorithm("PBEWithMD5AndTripleDES");
}
/**
* @param encryptText 密文
* @return java.lang.String 解密后文本
* @description 项目中使用JasyptUtil通用解密方法
* @date 2024/6/6 9:46
*/
public static String jasyptDecrypt4EndPoint(String encryptText) {
//以ENCRYPTOR_PREFIX常量值开头的便是密文需要解密
//否则认为是明文不再进行处理
if (encryptText.startsWith("enc`")) {
encryptText = encryptText.replace("enc`", "");
encryptText = JasyptUtil.basicDecrypted(encryptText);
}
return encryptText;
}
/**
* @param encryptText 待加密文本
* @return java.lang.String 加密后文本
* @description 使用BasicTextEncryptor对文本进行加密
* @date 2024/6/5 17:24
*/
public static String basicEncrypt(String encryptText) {
try {
// 加密文本信息
return basicTextEncryptor.encrypt(encryptText);
} catch (Exception e) {
// 处理加密/解密过程中可能出现的异常
log.error("Error during encryption: ", e);
}
return null;
}
/**
* @param decryptedText 待解密文本
* @return java.lang.String 解密后文本
* @description 使用BasicTextEncryptor对文本进行解密
* @date 2024/6/5 17:24
*/
public static String basicDecrypted(String decryptedText) {
try {
// 解密已加密的文本信息
return basicTextEncryptor.decrypt(decryptedText);
} catch (Exception e) {
// 处理加密/解密过程中可能出现的异常
log.error("Error during decryption: ", e);
}
return null;
}
/**
* @param encryptText 待加密文本
* @return java.lang.String 加密后文本
* @description 使用BasicPasswordEncryptor对文本进行加密
* @date 2024/6/5 17:24
*/
public static String basicPasswordEncrypt(String encryptText) {
try {
// 加密文本
return basicPasswordEncryptor.encryptPassword(encryptText);
} catch (Exception e) {
// 处理加密/解密过程中可能出现的异常
log.error("Error during encryption: ", e);
}
return null;
}
/**
* @param plainPassword 明文(如前端输入密码)
* @param encryptedPassword 密文(如数据库存储密码)
* @return boolean 校验通过返回true否则返回false
* @description 使用BasicPasswordEncryptor实例对输入密码进行验证
* @date 2024/6/5 17:45
*/
private static boolean oneWayPasswordCheck(String plainPassword, String encryptedPassword) {
// 检查密码 plainPassword 是否与加密后的密码匹配
return basicPasswordEncryptor.checkPassword(plainPassword, encryptedPassword);
}
/**
* @param encryptText 待加密文本
* @return java.lang.String 加密后文本
* @description 使用PooledPBEStringEncryptor对文本进行加密
* @date 2024/6/5 17:24
*/
private static String multiThreadEncrypted(String encryptText) {
// 加密明文字符串
return pooledPBEStringEncryptor.encrypt(encryptText);
}
/**
* @param decryptedText 加密文本
* @return java.lang.String 解密后文本
* @description 使用PooledPBEStringEncryptor对文本进行解密
* @date 2024/6/5 17:24
*/
private static String multiThreadDecrypted(String decryptedText) {
// 解密密文字符串
return pooledPBEStringEncryptor.decrypt(decryptedText);
}
/**
* 该示例演示如何改变加密算法。
* 该方法不接受参数且无返回值。
* 主要步骤包括创建加密器、设置密码和算法、加密数据以及解密数据。
* <p>
* 自定义地使用不同的算法进行加密解密
*/
private static void changeAlgorithmExample() {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
System.out.println("---------");
// 设置加密器的密码
//encryptor.setPassword(generateSecurePassword());
encryptor.setPassword("xxxxxxxxxxx");
// 设置加密器使用的加密算法
encryptor.setAlgorithm("PBEWithMD5AndTripleDES");
// 使用加密器对文本进行加密
String encryptedText = encryptor.encrypt("xxxxxxxxxxxxxxxx*");
System.out.println("encryptedText:" + encryptedText);
// 使用加密器对加密后的文本进行解密
String decryptedText = encryptor.decrypt(encryptedText);
System.out.println("decryptedText:" + decryptedText);
}
public static void main(String[] args) {
final String s = basicEncrypt("artisan");
System.out.println(s);
System.out.println(basicDecrypted(s));
}
}
org.springframework.boot.env.EnvironmentPostProcessor
部分配置文件 样例
小结
Spring Boot 的 EnvironmentPostProcessor
提供了在应用启动过程中的一个灵活扩展点,适用于有定制配置需求的场景。通过实现该接口,开发者可以在 Spring 应用上下文创建前调整环境配置,提供更强的动态控制和灵活性。
EnvironmentPostProcessor
是 Spring Boot 中的一个早期扩展点,允许开发者在Environment
初始化之后、ApplicationContext
初始化之前操作环境。- Spring Boot 使用
SpringFactoriesLoader
从META-INF/spring.factories
文件中加载EnvironmentPostProcessor
实现。 - 此扩展点适合用于动态加载配置、基于环境修改配置等场景。
通过 EnvironmentPostProcessor
,开发者可以在 Spring Boot 应用启动的早期阶段,实现对应用环境的自定义配置,以满足特定的需求。