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
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,9 +16,21 @@
|
||||
|
||||
package org.springframework.boot.env;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.aot.generate.GeneratedClass;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.ConfigurableBootstrapContext;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
||||
@@ -26,20 +38,29 @@ import org.springframework.boot.context.event.ApplicationFailedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||
import org.springframework.boot.logging.DeferredLogs;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.event.SmartApplicationListener;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link SmartApplicationListener} used to trigger {@link EnvironmentPostProcessor
|
||||
* EnvironmentPostProcessors} registered in the {@code spring.factories} file.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
|
||||
|
||||
private static final String AOT_FEATURE_NAME = "EnvironmentPostProcessor";
|
||||
|
||||
/**
|
||||
* The default order for the processor.
|
||||
*/
|
||||
@@ -104,8 +125,10 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
|
||||
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
|
||||
ConfigurableEnvironment environment = event.getEnvironment();
|
||||
SpringApplication application = event.getSpringApplication();
|
||||
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
|
||||
event.getBootstrapContext())) {
|
||||
List<EnvironmentPostProcessor> postProcessors = getEnvironmentPostProcessors(application.getResourceLoader(),
|
||||
event.getBootstrapContext());
|
||||
addAotGeneratedEnvironmentPostProcessorIfNecessary(postProcessors, application);
|
||||
for (EnvironmentPostProcessor postProcessor : postProcessors) {
|
||||
postProcessor.postProcessEnvironment(environment, application);
|
||||
}
|
||||
}
|
||||
@@ -129,6 +152,32 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
|
||||
return postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
|
||||
}
|
||||
|
||||
private void addAotGeneratedEnvironmentPostProcessorIfNecessary(List<EnvironmentPostProcessor> postProcessors,
|
||||
SpringApplication springApplication) {
|
||||
if (AotDetector.useGeneratedArtifacts()) {
|
||||
ClassLoader classLoader = (springApplication.getResourceLoader() != null)
|
||||
? springApplication.getResourceLoader().getClassLoader() : null;
|
||||
String postProcessorClassName = springApplication.getMainApplicationClass().getName() + "__"
|
||||
+ AOT_FEATURE_NAME;
|
||||
if (ClassUtils.isPresent(postProcessorClassName, classLoader)) {
|
||||
postProcessors.add(0, instantiateEnvironmentPostProcessor(postProcessorClassName, classLoader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EnvironmentPostProcessor instantiateEnvironmentPostProcessor(String postProcessorClassName,
|
||||
ClassLoader classLoader) {
|
||||
try {
|
||||
Class<?> initializerClass = ClassUtils.resolveClassName(postProcessorClassName, classLoader);
|
||||
Assert.isAssignable(EnvironmentPostProcessor.class, initializerClass);
|
||||
return (EnvironmentPostProcessor) BeanUtils.instantiateClass(initializerClass);
|
||||
}
|
||||
catch (BeanInstantiationException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to instantiate EnvironmentPostProcessor: " + postProcessorClassName, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
@@ -138,4 +187,63 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contribute a {@code <Application>__EnvironmentPostProcessor} class that stores AOT
|
||||
* optimizations.
|
||||
*/
|
||||
static class EnvironmentBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
|
||||
|
||||
@Override
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
Environment environment = beanFactory.getBean(ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME,
|
||||
Environment.class);
|
||||
String[] activeProfiles = environment.getActiveProfiles();
|
||||
String[] defaultProfiles = environment.getDefaultProfiles();
|
||||
if (!ObjectUtils.isEmpty(activeProfiles) && !Arrays.equals(activeProfiles, defaultProfiles)) {
|
||||
return new EnvironmentAotContribution(activeProfiles);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class EnvironmentAotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private static final String ENVIRONMENT_VARIABLE = "environment";
|
||||
|
||||
private final String[] activeProfiles;
|
||||
|
||||
private EnvironmentAotContribution(String[] activeProfiles) {
|
||||
this.activeProfiles = activeProfiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext,
|
||||
BeanFactoryInitializationCode beanFactoryInitializationCode) {
|
||||
GeneratedClass generatedClass = generationContext.getGeneratedClasses()
|
||||
.addForFeature(AOT_FEATURE_NAME, (type) -> {
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
type.addJavadoc("Configure the environment with AOT optimizations.");
|
||||
type.addSuperinterface(EnvironmentPostProcessor.class);
|
||||
});
|
||||
generatedClass.getMethods().add("postProcessEnvironment", (method) -> {
|
||||
method.addModifiers(Modifier.PUBLIC);
|
||||
method.addAnnotation(Override.class);
|
||||
method.addParameter(ConfigurableEnvironment.class, ENVIRONMENT_VARIABLE);
|
||||
method.addParameter(SpringApplication.class, "application");
|
||||
method.addCode(generateActiveProfilesInitializeCode());
|
||||
});
|
||||
}
|
||||
|
||||
private CodeBlock generateActiveProfilesInitializeCode() {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
for (String activeProfile : this.activeProfiles) {
|
||||
code.addStatement("$L.addActiveProfile($S)", ENVIRONMENT_VARIABLE, activeProfile);
|
||||
}
|
||||
return code.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ org.springframework.boot.web.server.MimeMappings.MimeMappingsRuntimeHints
|
||||
|
||||
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
|
||||
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor,\
|
||||
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.EnvironmentBeanFactoryInitializationAotProcessor,\
|
||||
org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor
|
||||
|
||||
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,25 +16,56 @@
|
||||
|
||||
package org.springframework.boot.env;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.BootstrapRegistry;
|
||||
import org.springframework.boot.DefaultBootstrapContext;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationFailedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationStartingEvent;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.EnvironmentBeanFactoryInitializationAotProcessor;
|
||||
import org.springframework.boot.logging.DeferredLogFactory;
|
||||
import org.springframework.boot.logging.DeferredLogs;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.aot.ApplicationContextAotGenerator;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.mock.env.MockPropertySource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
@@ -45,98 +76,286 @@ import static org.mockito.Mockito.spy;
|
||||
* Tests for {@link EnvironmentPostProcessorApplicationListener}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class EnvironmentPostProcessorApplicationListenerTests {
|
||||
|
||||
private final DeferredLogs deferredLogs = spy(new DeferredLogs());
|
||||
@Nested
|
||||
class ListenerTests {
|
||||
|
||||
private final DefaultBootstrapContext bootstrapContext = spy(new DefaultBootstrapContext());
|
||||
private final DeferredLogs deferredLogs = spy(new DeferredLogs());
|
||||
|
||||
private final EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener();
|
||||
private final DefaultBootstrapContext bootstrapContext = spy(new DefaultBootstrapContext());
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
ReflectionTestUtils.setField(this.listener, "deferredLogs", this.deferredLogs);
|
||||
ReflectionTestUtils.setField(this.listener, "postProcessorsFactory",
|
||||
(Function<ClassLoader, EnvironmentPostProcessorsFactory>) (
|
||||
classLoader) -> EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class));
|
||||
}
|
||||
private final EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener();
|
||||
|
||||
@Test
|
||||
void createUsesSpringFactories() {
|
||||
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener();
|
||||
assertThat(listener.getEnvironmentPostProcessors(null, this.bootstrapContext)).hasSizeGreaterThan(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasFactoryUsesFactory() {
|
||||
EnvironmentPostProcessorApplicationListener listener = EnvironmentPostProcessorApplicationListener
|
||||
.with(EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class));
|
||||
List<EnvironmentPostProcessor> postProcessors = listener.getEnvironmentPostProcessors(null,
|
||||
this.bootstrapContext);
|
||||
assertThat(postProcessors).hasSize(1);
|
||||
assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsEventTypeWhenApplicationEnvironmentPreparedEventReturnsTrue() {
|
||||
assertThat(this.listener.supportsEventType(ApplicationEnvironmentPreparedEvent.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsEventTypeWhenApplicationPreparedEventReturnsTrue() {
|
||||
assertThat(this.listener.supportsEventType(ApplicationPreparedEvent.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsEventTypeWhenApplicationFailedEventReturnsTrue() {
|
||||
assertThat(this.listener.supportsEventType(ApplicationFailedEvent.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsEventTypeWhenOtherEventReturnsFalse() {
|
||||
assertThat(this.listener.supportsEventType(ApplicationStartingEvent.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onApplicationEventWhenApplicationEnvironmentPreparedEventCallsPostProcessors() {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(this.bootstrapContext,
|
||||
application, new String[0], environment);
|
||||
this.listener.onApplicationEvent(event);
|
||||
assertThat(environment.getProperty("processed")).isEqualTo("true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onApplicationEventWhenApplicationPreparedEventSwitchesLogs() {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
ApplicationPreparedEvent event = new ApplicationPreparedEvent(application, new String[0], context);
|
||||
this.listener.onApplicationEvent(event);
|
||||
then(this.deferredLogs).should().switchOverAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onApplicationEventWhenApplicationFailedEventSwitchesLogs() {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
ApplicationFailedEvent event = new ApplicationFailedEvent(application, new String[0], context,
|
||||
new RuntimeException());
|
||||
this.listener.onApplicationEvent(event);
|
||||
then(this.deferredLogs).should().switchOverAll();
|
||||
}
|
||||
|
||||
static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
TestEnvironmentPostProcessor(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry) {
|
||||
assertThat(logFactory).isNotNull();
|
||||
assertThat(bootstrapRegistry).isNotNull();
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
ReflectionTestUtils.setField(this.listener, "deferredLogs", this.deferredLogs);
|
||||
ReflectionTestUtils.setField(this.listener, "postProcessorsFactory",
|
||||
(Function<ClassLoader, EnvironmentPostProcessorsFactory>) (
|
||||
classLoader) -> EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
((MockEnvironment) environment).setProperty("processed", "true");
|
||||
@Test
|
||||
void createUsesSpringFactories() {
|
||||
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener();
|
||||
assertThat(listener.getEnvironmentPostProcessors(null, this.bootstrapContext)).hasSizeGreaterThan(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasFactoryUsesFactory() {
|
||||
EnvironmentPostProcessorApplicationListener listener = EnvironmentPostProcessorApplicationListener
|
||||
.with(EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class));
|
||||
List<EnvironmentPostProcessor> postProcessors = listener.getEnvironmentPostProcessors(null,
|
||||
this.bootstrapContext);
|
||||
assertThat(postProcessors).hasSize(1);
|
||||
assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsEventTypeWhenApplicationEnvironmentPreparedEventReturnsTrue() {
|
||||
assertThat(this.listener.supportsEventType(ApplicationEnvironmentPreparedEvent.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsEventTypeWhenApplicationPreparedEventReturnsTrue() {
|
||||
assertThat(this.listener.supportsEventType(ApplicationPreparedEvent.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsEventTypeWhenApplicationFailedEventReturnsTrue() {
|
||||
assertThat(this.listener.supportsEventType(ApplicationFailedEvent.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsEventTypeWhenOtherEventReturnsFalse() {
|
||||
assertThat(this.listener.supportsEventType(ApplicationStartingEvent.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onApplicationEventWhenApplicationEnvironmentPreparedEventCallsPostProcessors() {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(this.bootstrapContext,
|
||||
application, new String[0], environment);
|
||||
this.listener.onApplicationEvent(event);
|
||||
assertThat(environment.getProperty("processed")).isEqualTo("true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onApplicationEventWhenApplicationPreparedEventSwitchesLogs() {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
ApplicationPreparedEvent event = new ApplicationPreparedEvent(application, new String[0], context);
|
||||
this.listener.onApplicationEvent(event);
|
||||
then(this.deferredLogs).should().switchOverAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onApplicationEventWhenApplicationFailedEventSwitchesLogs() {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
ApplicationFailedEvent event = new ApplicationFailedEvent(application, new String[0], context,
|
||||
new RuntimeException());
|
||||
this.listener.onApplicationEvent(event);
|
||||
then(this.deferredLogs).should().switchOverAll();
|
||||
}
|
||||
|
||||
static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
TestEnvironmentPostProcessor(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry) {
|
||||
assertThat(logFactory).isNotNull();
|
||||
assertThat(bootstrapRegistry).isNotNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
((MockEnvironment) environment).setProperty("processed", "true");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AotTests {
|
||||
|
||||
private static final ClassName TEST_APP = ClassName.get("com.example", "TestApp");
|
||||
|
||||
@Test
|
||||
void aotContributionIsNotNecessaryWithDefaultConfiguration() {
|
||||
assertThat(getContribution(new StandardEnvironment())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aotContributionIsNotNecessaryWithDefaultProfileActive() {
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
environment.setDefaultProfiles("fallback");
|
||||
environment.setActiveProfiles("fallback");
|
||||
assertThat(getContribution(environment)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aotContributionRegistersActiveProfiles() {
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
environment.setActiveProfiles("one", "two");
|
||||
compile(createContext(environment), (compiled) -> {
|
||||
EnvironmentPostProcessor environmentPostProcessor = compiled.getInstance(EnvironmentPostProcessor.class,
|
||||
ClassName.get("com.example", "TestApp__EnvironmentPostProcessor").toString());
|
||||
StandardEnvironment freshEnvironment = new StandardEnvironment();
|
||||
environmentPostProcessor.postProcessEnvironment(freshEnvironment, new SpringApplication());
|
||||
assertThat(freshEnvironment.getActiveProfiles()).containsExactly("one", "two");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseAotEnvironmentPostProcessor() {
|
||||
SpringApplication application = new SpringApplication(ExampleAotProcessedApp.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
application.setMainApplicationClass(ExampleAotProcessedApp.class);
|
||||
System.setProperty(AotDetector.AOT_ENABLED, "true");
|
||||
try {
|
||||
ApplicationContext context = application.run();
|
||||
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("one", "three");
|
||||
assertThat(context.getBean("test")).isEqualTo("test");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(AotDetector.AOT_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void aotEnvironmentPostProcessorShouldBeAppliedFirst(@TempDir Path tempDir) {
|
||||
Properties properties = new Properties();
|
||||
properties.put(EnvironmentPostProcessor.class.getName(), TestEnvironmentPostProcessor.class.getName());
|
||||
ClassLoader classLoader = createClassLoaderWithAdditionalSpringFactories(tempDir, properties);
|
||||
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader);
|
||||
|
||||
SpringApplication application = new SpringApplication(ExampleAotProcessedApp.class);
|
||||
application.setResourceLoader(resourceLoader);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
application.setMainApplicationClass(ExampleAotProcessedApp.class);
|
||||
System.setProperty(AotDetector.AOT_ENABLED, "true");
|
||||
try {
|
||||
ApplicationContext context = application.run();
|
||||
// See TestEnvironmentPostProcessor
|
||||
assertThat(context.getEnvironment().getProperty("test.activeProfiles")).isEqualTo("one,three");
|
||||
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("one", "three");
|
||||
assertThat(context.getBean("test")).isEqualTo("test");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(AotDetector.AOT_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeLenientIfAotEnvironmentPostProcessorDoesNotExist() {
|
||||
SpringApplication application = new SpringApplication(ExampleAotProcessedNoProfileApp.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
application.setMainApplicationClass(ExampleAotProcessedNoProfileApp.class);
|
||||
System.setProperty(AotDetector.AOT_ENABLED, "true");
|
||||
try {
|
||||
ApplicationContext context = application.run();
|
||||
assertThat(context.getEnvironment().getActiveProfiles()).isEmpty();
|
||||
assertThat(context.getBean("test")).isEqualTo("test");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(AotDetector.AOT_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
private BeanFactoryInitializationAotContribution getContribution(ConfigurableEnvironment environment) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton(ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME, environment);
|
||||
return new EnvironmentBeanFactoryInitializationAotProcessor().processAheadOfTime(beanFactory);
|
||||
}
|
||||
|
||||
private GenericApplicationContext createContext(ConfigurableEnvironment environment) {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.setEnvironment(environment);
|
||||
return context;
|
||||
}
|
||||
|
||||
private void compile(GenericApplicationContext context, Consumer<Compiled> compiled) {
|
||||
TestGenerationContext generationContext = new TestGenerationContext(TEST_APP);
|
||||
new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext);
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().with(generationContext).compile(compiled);
|
||||
}
|
||||
|
||||
private ClassLoader createClassLoaderWithAdditionalSpringFactories(Path tempDir, Properties properties) {
|
||||
return new ClassLoader() {
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
Enumeration<URL> resources = super.getResources(name);
|
||||
if (SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION.equals(name)) {
|
||||
Path springFactories = tempDir.resolve("spring.factories");
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(springFactories)) {
|
||||
properties.store(writer, "");
|
||||
}
|
||||
List<URL> allResources = new ArrayList<>();
|
||||
allResources.add(springFactories.toUri().toURL());
|
||||
allResources.addAll(Collections.list(resources));
|
||||
return Collections.enumeration(allResources);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
MockPropertySource propertySource = new MockPropertySource().withProperty("test.activeProfiles",
|
||||
StringUtils.arrayToCommaDelimitedString(environment.getActiveProfiles()));
|
||||
environment.getPropertySources().addLast(propertySource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ExampleAotProcessedApp {
|
||||
|
||||
}
|
||||
|
||||
static class ExampleAotProcessedApp__ApplicationContextInitializer
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
applicationContext.getBeanFactory().registerSingleton("test", "test");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ExampleAotProcessedApp__EnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
environment.addActiveProfile("one");
|
||||
environment.addActiveProfile("three");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ExampleAotProcessedNoProfileApp {
|
||||
|
||||
}
|
||||
|
||||
static class ExampleAotProcessedNoProfileApp__ApplicationContextInitializer
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
applicationContext.getBeanFactory().registerSingleton("test", "test");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user