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"); + } + } }