This tutorial shows you how to use the EnvironmentPostProcessor
in Spring Boot.
Spring Boot has an functional interface called EnvironmentPostProcessor
, which allows customization of the Environment
object before the application context is refreshed. There are several things you can do using the interface. For example, it can be used to set profiles, set property sources based on the values from environment variables, as well as validating environment properties. In this tutorial, I'm going to show you how to use the interface.
Using EnvironmentPostProcessor
EnvironmentPostProcessor
To define a EnvironmentPostProcessor
, you need to create a class that implements EnvironmentPostProcessor
. A non-abstract class that implements the interface has to override the following method.
void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
Below are several things you can do inside the method.
Set Profiles
The first parameter type, ConfigurableEnvironment
, has two methods for setting the active and default profiles. You can set the active and default profiles using the setActiveProfiles
and setDefaultProfiles
methods respectively. Both methods allow you to pass multiple values. There is another method named addActiveProfile
which is used to add a profile to the current set of active profiles.
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
Examples:
environment.setActiveProfiles("profileA", "profileB");
environment.addActiveProfile("profileC");
environment.setDefaultProfiles("profileD");
If you only want to set the profiles, there are other ways to set active profiles in Spring Boot which is easier than this one.
Get and Validate Profiles
Using the ConfirugrableEnvironment
object, you can get the active profiles by using the getActiveProfiles
. Then, you can validate the profiles by using your own logic.
private static final Set<String> ENV_PROFILES = Set.of("stg", "prod");
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Set<String> activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles()));
activeProfiles.retainAll(ENV_PROFILES);
if (activeProfiles.size() > 1) {
throw new IllegalStateException(String.format("Can only use one profile of %s", ENV_PROFILES));
}
}
Add Property Sources
It's also possible to add a property source inside the postProcessEnvironment
method. For example, the application needs to read a property value from the system environment variable.
WOOLHA="{\"id\": 100, \"name\":\"myname\", \"secret\": \"mysecret\"}"
We want to read and parse the value above and map each field to a property with a prefix.
com.woolha.id=100
com.woolha.name=myname
com.woolha.secret=mysecret
To read a value from an environment property, you can call the getProperty
method of the ConfigurableEnvironment
object. Since the value above is in JSON format, it has to be parsed first. Then, map the values to properties with a custom prefix.
After that, get the object that holds the property sources by calling the ConfigurableEnvironment
's getPropertySources
method, which returns MutablePropertySources
. On the MutablePropertySources
object, call the addAfter
method, which can be used to add a given property source object whose precedence is below the passed relative property source name.
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource
Example:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
JsonParser jsonParser = JsonParserFactory.getJsonParser();
Map<String, Object> woolhaProperties = jsonParser.parseMap(environment.getProperty("WOOLHA"));
Map<String, Object> prefixedProperties = woolhaProperties.entrySet().stream()
.collect(Collectors.toMap(
entry -> "com.woolha." + entry.getKey(),
Map.Entry::getValue
));
environment.getPropertySources()
.addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new MapPropertySource("woolha", prefixedProperties));
}
By doing so, it becomes possible to access the properties with the prefix.
@Value("${com.woolha.id}")
int woolhaId;
@Value("${com.woolha.name}")
String woolhaName;
@Value("${com.woolha.secret}")
String woolhaSecret;
Validate Properties
Another thing that you can do is validate that certain properties must be present and not null. Since the ConfigurableEnvironment
extends the ConfigurablePropertyResolver
interface, it has setRequiredProperties
and validateRequiredProperties
methods.
void setRequiredProperties(String... requiredProperties);
void validateRequiredProperties() throws MissingRequiredPropertiesException;
You have to set the required properties using the setRequiredProperties
method. Then, call validateRequiredProperties
to perform the validation.
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
environment.setRequiredProperties("MYPROP1", "MYPROP2");
environment.validateRequiredProperties();
}
Register EnvironmentPostProcessor
in spring.factories
After you create the class, it's not automatically registered. Therefore, you may find that it's not loaded by Spring when the application is being started. The solution is you have to register it in the spring.factories
file. The file must be placed in src/main/resources/META-INF
directory. If it doesn't exist, you have to create it.
Inside the file, you have to define a key named org.springframework.boot.env.EnvironmentPostProcessor
. The value must be the fully qualified name of the class which includes the package name.
org.springframework.boot.env.EnvironmentPostProcessor=\
com.woolha.spring.example.config.MyPostProcessor
If there are multiple classes, use a comma as the delimiters.
org.springframework.boot.env.EnvironmentPostProcessor=\
com.woolha.spring.example.config.MyPostProcessor,\
com.woolha.spring.environmentpostprocessor.config.MyAnotherPostProcessor
Set EnvironmentPostProcessor
Order
If there are multiple EnvironmentPostProcessor
objects, you may need to define the processing order. The recommended way to set the order is by using the @Order
annotation. Spring loads classes with higher precedence first. The lower the integer value passed to the annotation, the higher the precedence. If there are some classes with the same @Order
value or not using the annotation, the order how they are defined in the spring.factories
determines the order Spring loads them.
@Order(Ordered.LOWEST_PRECEDENCE)
public class MyPostProcessor implements EnvironmentPostProcessor {
// class content
}
Add Constructor Parameters
Since Spring Boot 2.4, it's also possible to add some parameters to the constructor. The first supported parameter type is DeferredLogFactory
, which is a factory for creating DeferredLog
instances. It can be used to create loggers whose messages are deferred until the logging system is fully initialized. Another parameter type that you can add is ConfigurableBootstrapContext
, which is used to store expensive objects or objects to be shared.
public class MyPostProcessor implements EnvironmentPostProcessor {
private final Log logger;
private final ConfigurableBootstrapContext configurableBootstrapContext;
public MyPostProcessor(
DeferredLogFactory logFactory,
ConfigurableBootstrapContext configurableBootstrapContext
) {
this.logger = logFactory.getLog(MyPostProcessor.class);
this.configurableBootstrapContext = configurableBootstrapContext;
}
}
Summary
In this tutorial, we have learned how to create and register EnvironmentPostProcessor
. Basically, you have to create a class that extends the interface and implement the postProcessEnvironment
. The class must be registered in the spring.factories
file.
You can also read about: