Spring系统属性解析器定制:Bean注入前如何修改属性?
Great question! Your current custom placeholder configurer approach works, but Spring (especially Spring Boot) offers cleaner, more integrated ways to tweak properties after they're collected but before beans are injected. Here are the most straightforward options:
1. Use EnvironmentPostProcessor (Spring Boot Recommended)
This is the most flexible way to modify the entire application environment early in the startup lifecycle—right after environment sources (system properties, env vars, application.properties) are loaded, but before any beans are created.
Step 1: Implement the Processor
Create a class that implements EnvironmentPostProcessor:
import org.springframework.boot.SpringApplication; 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 java.util.Properties; public class CustomPropertyModifier implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { // Access existing properties from env vars, system props, etc. String originalDbUrl = environment.getProperty("db.url"); // Apply your custom modification logic Properties modifiedProps = new Properties(); modifiedProps.setProperty("db.url", tweakDbUrl(originalDbUrl)); // Add modified properties to the environment (higher priority to override originals) MutablePropertySources propertySources = environment.getPropertySources(); propertySources.addFirst(new PropertiesPropertySource("custom-modified-props", modifiedProps)); } private String tweakDbUrl(String original) { // Example: Replace localhost with production DB endpoint return original.replace("localhost", "prod-db.example.com"); } }
Step 2: Register the Processor
Create a file META-INF/spring.factories in your src/main/resources directory, and add:
org.springframework.boot.env.EnvironmentPostProcessor=com.yourpackage.CustomPropertyModifier
Spring will automatically pick up this processor during startup.
2. Extend PropertySourcesPlaceholderConfigurer
If you want to hook directly into the placeholder resolution process (streamlining your existing approach), extend the default configurer and override processProperties:
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.ConfigurableListableBeanFactory; import org.springframework.core.env.PropertySources; import org.springframework.core.env.MapPropertySource; public class CustomPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer { @Override protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, PropertySources propertySources) { // Modify properties before placeholder resolution runs propertySources.forEach(source -> { if (source instanceof MapPropertySource mapSource) { // Example: Add connection timeout to all DB-related properties mapSource.getSource().replaceAll((key, value) -> { if (key.startsWith("db.")) { return ((String) value) + "?connectionTimeout=5000"; } return value; }); } }); // Let Spring's default placeholder processing take over super.processProperties(beanFactoryToProcess, propertySources); } }
Register it as a bean in your configuration:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class PropertyConfig { @Bean public static PropertySourcesPlaceholderConfigurer customPlaceholderConfigurer() { return new CustomPlaceholderConfigurer(); } }
3. Use ApplicationListener for ApplicationEnvironmentPreparedEvent
If you prefer an event-driven approach, listen to the ApplicationEnvironmentPreparedEvent—this fires right after the environment is prepared, similar to EnvironmentPostProcessor:
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; public class EnvironmentPreparedListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> { @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment(); // Modify properties directly in the environment String originalApiKey = environment.getProperty("api.key"); environment.getSystemProperties().put("api.key", encryptApiKey(originalApiKey)); } private String encryptApiKey(String original) { // Example encryption logic (replace with your own) return "encrypted_" + original; } }
Register the listener in your main application class:
public class YourSpringApplication { public static void main(String[] args) { SpringApplication app = new SpringApplication(YourSpringApplication.class); app.addListeners(new EnvironmentPreparedListener()); app.run(args); } }
Which One Should You Choose?
EnvironmentPostProcessor: Best for global, early-stage property modifications (recommended for most use cases).- Extended
PropertySourcesPlaceholderConfigurer: Ideal if you need to modify properties specifically during placeholder resolution (closer to your existing workflow but cleaner). ApplicationListener: Great if you want event-driven logic or need to coordinate with other startup events.
All these approaches eliminate manual system property setup and integrate natively with Spring's lifecycle.
内容的提问来源于stack exchange,提问作者Chad Van De Hey

