Skip to content

Commit 6b216f1

Browse files
committed
Apply active profiles consistently with AOT
Profiles that are active during AOT processing are automatically enabled when the AOT initializer runs. While this works for an arrangement that only relies on the ApplicationContext, it does not for Spring Boot that has specific handling of profiles when it prepares the environment, way before the ApplicationContext is event created. This commit adds a specific contribution that generates a dedicated EnvironmentPostProcessor. It also updates the handling of post processors so that when AOT runs, the AOT generated one if it exists is invoked first. This has the effect of consistently activating such profiles in a Spring Boot application. Closes gh-41562
1 parent 54dcd98 commit 6b216f1

File tree

3 files changed

+404
-76
lines changed

3 files changed

+404
-76
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,30 +16,51 @@
1616

1717
package org.springframework.boot.env;
1818

19+
import java.util.Arrays;
1920
import java.util.List;
2021
import java.util.function.Function;
2122

23+
import javax.lang.model.element.Modifier;
24+
25+
import org.springframework.aot.AotDetector;
26+
import org.springframework.aot.generate.GeneratedClass;
27+
import org.springframework.aot.generate.GenerationContext;
28+
import org.springframework.beans.BeanInstantiationException;
29+
import org.springframework.beans.BeanUtils;
30+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
31+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
32+
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
33+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2234
import org.springframework.boot.ConfigurableBootstrapContext;
2335
import org.springframework.boot.SpringApplication;
2436
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
2537
import org.springframework.boot.context.event.ApplicationFailedEvent;
2638
import org.springframework.boot.context.event.ApplicationPreparedEvent;
2739
import org.springframework.boot.logging.DeferredLogs;
2840
import org.springframework.context.ApplicationEvent;
41+
import org.springframework.context.ConfigurableApplicationContext;
2942
import org.springframework.context.event.SmartApplicationListener;
3043
import org.springframework.core.Ordered;
3144
import org.springframework.core.env.ConfigurableEnvironment;
45+
import org.springframework.core.env.Environment;
3246
import org.springframework.core.io.ResourceLoader;
47+
import org.springframework.javapoet.CodeBlock;
48+
import org.springframework.util.Assert;
49+
import org.springframework.util.ClassUtils;
50+
import org.springframework.util.ObjectUtils;
3351

3452
/**
3553
* {@link SmartApplicationListener} used to trigger {@link EnvironmentPostProcessor
3654
* EnvironmentPostProcessors} registered in the {@code spring.factories} file.
3755
*
3856
* @author Phillip Webb
57+
* @author Stephane Nicoll
3958
* @since 2.4.0
4059
*/
4160
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
4261

62+
private static final String AOT_FEATURE_NAME = "EnvironmentPostProcessor";
63+
4364
/**
4465
* The default order for the processor.
4566
*/
@@ -104,8 +125,10 @@ public void onApplicationEvent(ApplicationEvent event) {
104125
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
105126
ConfigurableEnvironment environment = event.getEnvironment();
106127
SpringApplication application = event.getSpringApplication();
107-
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
108-
event.getBootstrapContext())) {
128+
List<EnvironmentPostProcessor> postProcessors = getEnvironmentPostProcessors(application.getResourceLoader(),
129+
event.getBootstrapContext());
130+
addAotGeneratedEnvironmentPostProcessorIfNecessary(postProcessors, application);
131+
for (EnvironmentPostProcessor postProcessor : postProcessors) {
109132
postProcessor.postProcessEnvironment(environment, application);
110133
}
111134
}
@@ -129,6 +152,32 @@ List<EnvironmentPostProcessor> getEnvironmentPostProcessors(ResourceLoader resou
129152
return postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
130153
}
131154

155+
private void addAotGeneratedEnvironmentPostProcessorIfNecessary(List<EnvironmentPostProcessor> postProcessors,
156+
SpringApplication springApplication) {
157+
if (AotDetector.useGeneratedArtifacts()) {
158+
ClassLoader classLoader = (springApplication.getResourceLoader() != null)
159+
? springApplication.getResourceLoader().getClassLoader() : null;
160+
String postProcessorClassName = springApplication.getMainApplicationClass().getName() + "__"
161+
+ AOT_FEATURE_NAME;
162+
if (ClassUtils.isPresent(postProcessorClassName, classLoader)) {
163+
postProcessors.add(0, instantiateEnvironmentPostProcessor(postProcessorClassName, classLoader));
164+
}
165+
}
166+
}
167+
168+
private EnvironmentPostProcessor instantiateEnvironmentPostProcessor(String postProcessorClassName,
169+
ClassLoader classLoader) {
170+
try {
171+
Class<?> initializerClass = ClassUtils.resolveClassName(postProcessorClassName, classLoader);
172+
Assert.isAssignable(EnvironmentPostProcessor.class, initializerClass);
173+
return (EnvironmentPostProcessor) BeanUtils.instantiateClass(initializerClass);
174+
}
175+
catch (BeanInstantiationException ex) {
176+
throw new IllegalArgumentException(
177+
"Failed to instantiate EnvironmentPostProcessor: " + postProcessorClassName, ex);
178+
}
179+
}
180+
132181
@Override
133182
public int getOrder() {
134183
return this.order;
@@ -138,4 +187,63 @@ public void setOrder(int order) {
138187
this.order = order;
139188
}
140189

190+
/**
191+
* Contribute a {@code <Application>__EnvironmentPostProcessor} class that stores AOT
192+
* optimizations.
193+
*/
194+
static class EnvironmentBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
195+
196+
@Override
197+
public BeanFactoryInitializationAotContribution processAheadOfTime(
198+
ConfigurableListableBeanFactory beanFactory) {
199+
Environment environment = beanFactory.getBean(ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME,
200+
Environment.class);
201+
String[] activeProfiles = environment.getActiveProfiles();
202+
String[] defaultProfiles = environment.getDefaultProfiles();
203+
if (!ObjectUtils.isEmpty(activeProfiles) && !Arrays.equals(activeProfiles, defaultProfiles)) {
204+
return new EnvironmentAotContribution(activeProfiles);
205+
}
206+
return null;
207+
}
208+
209+
}
210+
211+
private static final class EnvironmentAotContribution implements BeanFactoryInitializationAotContribution {
212+
213+
private static final String ENVIRONMENT_VARIABLE = "environment";
214+
215+
private final String[] activeProfiles;
216+
217+
private EnvironmentAotContribution(String[] activeProfiles) {
218+
this.activeProfiles = activeProfiles;
219+
}
220+
221+
@Override
222+
public void applyTo(GenerationContext generationContext,
223+
BeanFactoryInitializationCode beanFactoryInitializationCode) {
224+
GeneratedClass generatedClass = generationContext.getGeneratedClasses()
225+
.addForFeature(AOT_FEATURE_NAME, (type) -> {
226+
type.addModifiers(Modifier.PUBLIC);
227+
type.addJavadoc("Configure the environment with AOT optimizations.");
228+
type.addSuperinterface(EnvironmentPostProcessor.class);
229+
});
230+
generatedClass.getMethods().add("postProcessEnvironment", (method) -> {
231+
method.addModifiers(Modifier.PUBLIC);
232+
method.addAnnotation(Override.class);
233+
method.addParameter(ConfigurableEnvironment.class, ENVIRONMENT_VARIABLE);
234+
method.addParameter(SpringApplication.class, "application");
235+
method.addCode(generateActiveProfilesInitializeCode());
236+
});
237+
}
238+
239+
private CodeBlock generateActiveProfilesInitializeCode() {
240+
CodeBlock.Builder code = CodeBlock.builder();
241+
for (String activeProfile : this.activeProfiles) {
242+
code.addStatement("$L.addActiveProfile($S)", ENVIRONMENT_VARIABLE, activeProfile);
243+
}
244+
return code.build();
245+
}
246+
247+
}
248+
141249
}

spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ org.springframework.boot.web.server.MimeMappings.MimeMappingsRuntimeHints
1515

1616
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
1717
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor,\
18+
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.EnvironmentBeanFactoryInitializationAotProcessor,\
1819
org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor
1920

2021
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\

0 commit comments

Comments
 (0)