spring-framework
spring-framework copied to clipboard
ActiveProfile is not picked up, if customize the Environment before it starts
Affects: spring-core 5.3.18
I'm tried to set active profile by customizing the Environmvent. Created simple spring-boot application 2.6.6 as below.
@SpringBootApplication
public class Spring5318activeprofileApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(Spring5318activeprofileApplication.class)
.listeners(new MyListener())
.build(args)
.run(args);
}
}
Add listener, based on sample https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.application.customize-the-environment-or-application-context
public class MyListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
Resource path = new ClassPathResource("cfg.yml");
PropertySource<?> propertySource = loadYaml(path);
environment.getPropertySources().addLast(propertySource);
}
private PropertySource<?> loadYaml(Resource path) {
Assert.isTrue(path.exists(), () -> "Resource " + path + " does not exist");
try {
return this.loader.load("custom-resource", path).get(0);
} catch (IOException ex) {
throw new IllegalStateException("Failed to load yaml configuration from " + path, ex);
}
}
}
cfg.yml is simple
spring:
profiles:
active: p1
This profile is not picked up (if debug) by org\springframework\core\env\AbstractEnvironment.java after adding custom PropertySource
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
String profiles = doGetActiveProfilesProperty();
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
Variable profiles is null because doGetActiveProfilesProperty() return null. So active profile is not set. Looks like it's incorrect behaviour.
In earlier version, for example in spring-core 5.1.7, there is little difference in getting profile variable but it worked.
org\springframework\core\env\AbstractEnvironment was:
protected Set<String> doGetActiveProfiles() {
synchronized(this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
String profiles = this.getProperty("spring.profiles.active");
if (StringUtils.hasText(profiles)) {
this.setActiveProfiles(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
In debug for spring-core 5.3.18
String profiles = doGetActiveProfilesProperty(); --> gives null -- it's INCORRECT
String profiles = this.getProperty("spring.profiles.active"); ---> gives p1 -- it's correct
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.and</groupId>
<artifactId>spring5318activeprofile</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring5318activeprofile</name>
<description>check active profile</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Same issue with Spring Framework 5.3.22 and Spring Boot 2.7.0.
Hoping this issue from April can get some attention.
@felipelo @alexandgit I don't understand how that could ever have worked. ApplicationEnvironmentPreparedEvent
, as its name indicates, is triggered when the environment has been prepared. It is too late to add something to the environment and expects to be take into account. If it worked before, it might have been by accident. Can we take a step back and can you share what you're trying to do?
If "It is too late to add something to the environment and expects to be take into account." then when it is not too late ? Not sure about your "too late" becase it's a try to customize the Environment before the application context is created.
According to https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-events-and-listeners, ApplicationStartingEvent
is only before ApplicationEnvironmentPreparedEvent
, but imho it's not applicable for setting environment.
BTW
According to https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.application.customize-the-environment-or-application-context "1.3. Customize the Environment or ApplicationContext Before It Starts" that is state "It is also possible to customize the Environment before the application context is refreshed by using EnvironmentPostProcessor", the example of using "MyEnvironmentPostProcessor implements EnvironmentPostProcessor
" also does not work for above case.
Simply change using MyListener to MyEnvironmentPostProcessor and debug it (AbstractEnvironment is in accent).
If "It is too late to add something to the environment and expects to be take into account." then when it is not too late ? Not sure about your "too late" becase it's a try to customize the Environment before the application context is created
The Javadoc of ApplicationPreparedEvent
states:
Event published as when a SpringApplication is starting up and the ApplicationContext is fully prepared but not refreshed. The bean definitions will be loaded and the Environment is ready for use at this stage.
You can't expect to be able to set a profile that conditions what property sources can be loaded at that time, simply because it has already been done.
It is also possible to customize the Environment before the application context is refreshed by using EnvironmentPostProcessor
Yes, it's part of the callback above. So those post processors are going to be triggered before the event is fired, to allow you to add property source, remove or reorder them, etc. But the default behavior of loading the relevant property sources will have already been done at that time, simply to let you reorder and/or remove some programmatically.
here's another quote of the doc:
You can programmatically set active profiles by calling SpringApplication.setAdditionalProfiles(…) before your application runs. It is also possible to activate profiles by using Spring’s ConfigurableEnvironment interface.
The "before your application runs" is key here. This section in the doc talks about command line switch or setting a property in your application configuration.