From 6b216f1748109de49c857187eaafefdc87b0dd8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 1 Oct 2024 13:48:07 +0200 Subject: [PATCH] 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 --- ...nmentPostProcessorApplicationListener.java | 114 +++++- .../resources/META-INF/spring/aot.factories | 1 + ...PostProcessorApplicationListenerTests.java | 365 ++++++++++++++---- 3 files changed, 404 insertions(+), 76 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java index 1bf9bd2b75f..a1b372b627a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java @@ -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 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 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 __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(); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories index d938a89eda1..451a2ae4b49 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories @@ -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=\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java index 40145f2ce7a..07b1f601bef 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java @@ -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.of(TestEnvironmentPostProcessor.class)); - } + private final EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(); - @Test - void createUsesSpringFactories() { - EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(); - assertThat(listener.getEnvironmentPostProcessors(null, this.bootstrapContext)).hasSizeGreaterThan(1); - } + @BeforeEach + void setup() { + ReflectionTestUtils.setField(this.listener, "deferredLogs", this.deferredLogs); + ReflectionTestUtils.setField(this.listener, "postProcessorsFactory", + (Function) ( + classLoader) -> EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class)); + } - @Test - void createWhenHasFactoryUsesFactory() { - EnvironmentPostProcessorApplicationListener listener = EnvironmentPostProcessorApplicationListener - .with(EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class)); - List postProcessors = listener.getEnvironmentPostProcessors(null, - this.bootstrapContext); - assertThat(postProcessors).hasSize(1); - assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); - } + @Test + void createUsesSpringFactories() { + EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(); + assertThat(listener.getEnvironmentPostProcessors(null, this.bootstrapContext)).hasSizeGreaterThan(1); + } - @Test - void supportsEventTypeWhenApplicationEnvironmentPreparedEventReturnsTrue() { - assertThat(this.listener.supportsEventType(ApplicationEnvironmentPreparedEvent.class)).isTrue(); - } + @Test + void createWhenHasFactoryUsesFactory() { + EnvironmentPostProcessorApplicationListener listener = EnvironmentPostProcessorApplicationListener + .with(EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class)); + List postProcessors = listener.getEnvironmentPostProcessors(null, + this.bootstrapContext); + assertThat(postProcessors).hasSize(1); + assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); + } - @Test - void supportsEventTypeWhenApplicationPreparedEventReturnsTrue() { - assertThat(this.listener.supportsEventType(ApplicationPreparedEvent.class)).isTrue(); - } + @Test + void supportsEventTypeWhenApplicationEnvironmentPreparedEventReturnsTrue() { + assertThat(this.listener.supportsEventType(ApplicationEnvironmentPreparedEvent.class)).isTrue(); + } - @Test - void supportsEventTypeWhenApplicationFailedEventReturnsTrue() { - assertThat(this.listener.supportsEventType(ApplicationFailedEvent.class)).isTrue(); - } + @Test + void supportsEventTypeWhenApplicationPreparedEventReturnsTrue() { + assertThat(this.listener.supportsEventType(ApplicationPreparedEvent.class)).isTrue(); + } - @Test - void supportsEventTypeWhenOtherEventReturnsFalse() { - assertThat(this.listener.supportsEventType(ApplicationStartingEvent.class)).isFalse(); - } + @Test + void supportsEventTypeWhenApplicationFailedEventReturnsTrue() { + assertThat(this.listener.supportsEventType(ApplicationFailedEvent.class)).isTrue(); + } - @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 supportsEventTypeWhenOtherEventReturnsFalse() { + assertThat(this.listener.supportsEventType(ApplicationStartingEvent.class)).isFalse(); + } - @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 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"); + } + + } - @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 { + @Nested + class AotTests { - TestEnvironmentPostProcessor(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry) { - assertThat(logFactory).isNotNull(); - assertThat(bootstrapRegistry).isNotNull(); + 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); + } } - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { - ((MockEnvironment) environment).setProperty("processed", "true"); + @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) { + 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 getResources(String name) throws IOException { + Enumeration 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 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 { + + @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 { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + applicationContext.getBeanFactory().registerSingleton("test", "test"); + } + } }