spring-framework icon indicating copy to clipboard operation
spring-framework copied to clipboard

ActiveProfile is not picked up, if customize the Environment before it starts

Open alexandgit opened this issue 2 years ago • 3 comments

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>

alexandgit avatar Apr 15 '22 12:04 alexandgit

Same issue with Spring Framework 5.3.22 and Spring Boot 2.7.0.

Hoping this issue from April can get some attention.

felipelo avatar Nov 15 '22 14:11 felipelo

@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?

snicoll avatar Nov 15 '22 14:11 snicoll

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).

alexandgit avatar Nov 17 '22 11:11 alexandgit

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.

snicoll avatar Oct 03 '23 08:10 snicoll