From e370b592d66ad9b01ff2bfff4b5991f0ac3a9751 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 13 Aug 2015 10:42:39 +0100 Subject: [PATCH] Introduce defined extension point for modifying the environment The commit introduces a new extension point, EnvironmentPostProcessor, that can be implemented by classes that want to modify the environment. Implementations of EnvironmentPostProcessor are loaded via spring.factories and called in response to the ApplicationEnvironmentPreparedEvent. Application listeners that wish to work with the post-processed environment can continue to listen to ApplicationEnvironmentPreparedEvent and order themselves to run after EnvironmentPostProcessingApplicationListener. Existing ApplicationListeners that modify the environment have, where possible, been updated to implement EnvironmentPostProcessor instead. Closes gh-3737 --- .../boot/SpringApplication.java | 2 +- ...java => VcapEnvironmentPostProcessor.java} | 23 ++-- .../config/AnsiOutputApplicationListener.java | 6 +- ...> ConfigFileEnvironmentPostProcessor.java} | 47 +++----- .../boot/context/config/package-info.java | 2 +- ...mentPostProcessingApplicationListener.java | 61 ++++++++++ .../boot/env/EnvironmentPostProcessor.java | 41 +++++++ ...nfigFileApplicationContextInitializer.java | 6 +- .../main/resources/META-INF/spring.factories | 9 +- ...=> VcapEnvironmentPostProcessorTests.java} | 24 ++-- ...figFileEnvironmentPostProcessorTests.java} | 111 ++++++++---------- 11 files changed, 204 insertions(+), 128 deletions(-) rename spring-boot/src/main/java/org/springframework/boot/cloudfoundry/{VcapApplicationListener.java => VcapEnvironmentPostProcessor.java} (91%) rename spring-boot/src/main/java/org/springframework/boot/context/config/{ConfigFileApplicationListener.java => ConfigFileEnvironmentPostProcessor.java} (91%) create mode 100644 spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessingApplicationListener.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessor.java rename spring-boot/src/test/java/org/springframework/boot/cloudfoundry/{VcapApplicationListenerTests.java => VcapEnvironmentPostProcessorTests.java} (87%) rename spring-boot/src/test/java/org/springframework/boot/context/config/{ConfigFileApplicationListenerTests.java => ConfigFileEnvironmentPostProcessorTests.java} (87%) diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 67605a73b9..bda73405b7 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -455,7 +455,7 @@ public class SpringApplication { * @param environment this application's environment * @param args arguments passed to the {@code run} method * @see #configureEnvironment(ConfigurableEnvironment, String[]) - * @see org.springframework.boot.context.config.ConfigFileApplicationListener + * @see org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor */ protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { environment.getActiveProfiles(); // ensure they are initialized diff --git a/spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapEnvironmentPostProcessor.java similarity index 91% rename from spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapApplicationListener.java rename to spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapEnvironmentPostProcessor.java index 8144c8f467..44e4bc6e5f 100644 --- a/spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapApplicationListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapEnvironmentPostProcessor.java @@ -25,11 +25,11 @@ import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.boot.context.config.ConfigFileApplicationListener; -import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor; +import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.json.JsonParser; import org.springframework.boot.json.JsonParserFactory; -import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; import org.springframework.core.env.CommandLinePropertySource; import org.springframework.core.env.ConfigurableEnvironment; @@ -39,8 +39,8 @@ import org.springframework.core.env.PropertiesPropertySource; import org.springframework.util.StringUtils; /** - * An {@link ApplicationListener} that knows where to find VCAP (a.k.a. Cloud Foundry) - * meta data in the existing environment. It parses out the VCAP_APPLICATION and + * An {@link EnvironmentPostProcessor} that knows where to find VCAP (a.k.a. Cloud + * Foundry) meta data in the existing environment. It parses out the VCAP_APPLICATION and * VCAP_SERVICES meta data and dumps it in a form that is easily consumed by * {@link Environment} users. If the app is running in Cloud Foundry then both meta data * items are JSON objects encoded in OS environment variables. VCAP_APPLICATION is a @@ -86,18 +86,19 @@ import org.springframework.util.StringUtils; * Cloud is more convenient and more robust against potential changes in Cloud Foundry. * * @author Dave Syer + * @author Andy Wilkinson */ -public class VcapApplicationListener implements - ApplicationListener, Ordered { +public class VcapEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { - private static final Log logger = LogFactory.getLog(VcapApplicationListener.class); + private static final Log logger = LogFactory + .getLog(VcapEnvironmentPostProcessor.class); private static final String VCAP_APPLICATION = "VCAP_APPLICATION"; private static final String VCAP_SERVICES = "VCAP_SERVICES"; // Before ConfigFileApplicationListener so values there can use these ones - private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1; + private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER - 1; private final JsonParser parser = JsonParserFactory.getJsonParser(); @@ -111,8 +112,8 @@ public class VcapApplicationListener implements } @Override - public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { - ConfigurableEnvironment environment = event.getEnvironment(); + public void postProcessEnvironment(ConfigurableEnvironment environment, + SpringApplication application) { if (!environment.containsProperty(VCAP_APPLICATION) && !environment.containsProperty(VCAP_SERVICES)) { return; diff --git a/spring-boot/src/main/java/org/springframework/boot/context/config/AnsiOutputApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/context/config/AnsiOutputApplicationListener.java index d85cbef391..007b99397b 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/config/AnsiOutputApplicationListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/config/AnsiOutputApplicationListener.java @@ -20,6 +20,7 @@ import org.springframework.boot.ansi.AnsiOutput; import org.springframework.boot.ansi.AnsiOutput.Enabled; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.env.EnvironmentPostProcessingApplicationListener; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; @@ -51,8 +52,9 @@ public class AnsiOutputApplicationListener implements @Override public int getOrder() { - // Apply after the ConfigFileApplicationListener - return ConfigFileApplicationListener.DEFAULT_ORDER + 1; + // Apply after the EnvironmentPostProcessingApplicationListener has called all + // EnvironmentPostProcessors + return EnvironmentPostProcessingApplicationListener.ORDER + 1; } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessor.java similarity index 91% rename from spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java rename to spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessor.java index 0b58d45192..4df9c7380a 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessor.java @@ -33,12 +33,11 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.bind.PropertiesConfigurationFactory; -import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.env.EnumerableCompositePropertySource; +import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.PropertySourcesLoader; import org.springframework.boot.logging.DeferredLog; -import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ConfigurationClassPostProcessor; @@ -47,7 +46,6 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.io.DefaultResourceLoader; @@ -59,7 +57,7 @@ import org.springframework.util.StringUtils; import org.springframework.validation.BindException; /** - * {@link ApplicationListener} that configures the context environment by loading + * {@link EnvironmentPostProcessor} that configures the context environment by loading * properties from well known file locations. By default properties will be loaded from * 'application.properties' and/or 'application.yml' files in the following locations: *