From 833aac2b26a935d55328b994d90c6ffb80dcd55a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 28 Oct 2015 18:58:19 +0000 Subject: [PATCH] Drive EnvironmentPostProcessors from ConfigFileApplicationListener MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, ConfigFileApplicationListener was listed in spring.factories as both an EnvironmentPostProcessor and an ApplicationListener. This was problematic as ConfigFileApplicationListener is stateful and listing it twice lead to two separate instances with separate state. This commit restore ConfigFileApplicationListener to only being an ApplicationListener. The driving of EnvironmentPostProcessors that was performed by EnvironmentPostProcessingApplicationListener is now performed by ConfigFileApplicationListener which adds itself as an EnvironmentPostProcessor. This ensures that there’s only a single instance of ConfigFileApplicationListener, allowing its state to be managed correctly. Closes gh-4258 --- .../devtools/RemoteSpringApplication.java | 6 +- .../boot/SpringApplication.java | 2 +- ...udFoundryVcapEnvironmentPostProcessor.java | 6 +- .../config/AnsiOutputApplicationListener.java | 5 +- ...ava => ConfigFileApplicationListener.java} | 59 ++++++++++++----- .../boot/context/config/package-info.java | 2 +- ...mentPostProcessingApplicationListener.java | 64 ------------------- ...nfigFileApplicationContextInitializer.java | 6 +- .../main/resources/META-INF/spring.factories | 6 +- ...> ConfigFileApplicationListenerTests.java} | 8 +-- 10 files changed, 60 insertions(+), 104 deletions(-) rename spring-boot/src/main/java/org/springframework/boot/context/config/{ConfigFileEnvironmentPostProcessor.java => ConfigFileApplicationListener.java} (91%) delete mode 100644 spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessingApplicationListener.java rename spring-boot/src/test/java/org/springframework/boot/context/config/{ConfigFileEnvironmentPostProcessorTests.java => ConfigFileApplicationListenerTests.java} (99%) diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java index 0183ad31c5..dff13a86cc 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java @@ -24,12 +24,11 @@ import org.springframework.boot.Banner; import org.springframework.boot.ResourceBanner; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.config.AnsiOutputApplicationListener; -import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor; +import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.boot.devtools.remote.client.RemoteClientConfiguration; import org.springframework.boot.devtools.restart.RestartInitializer; import org.springframework.boot.devtools.restart.RestartScopeInitializer; import org.springframework.boot.devtools.restart.Restarter; -import org.springframework.boot.env.EnvironmentPostProcessingApplicationListener; import org.springframework.boot.logging.ClasspathLoggingApplicationListener; import org.springframework.boot.logging.LoggingApplicationListener; import org.springframework.context.ApplicationContextInitializer; @@ -73,8 +72,7 @@ public final class RemoteSpringApplication { private Collection> getListeners() { List> listeners = new ArrayList>(); listeners.add(new AnsiOutputApplicationListener()); - listeners.add(new ConfigFileEnvironmentPostProcessor()); - listeners.add(new EnvironmentPostProcessingApplicationListener()); + listeners.add(new ConfigFileApplicationListener()); listeners.add(new ClasspathLoggingApplicationListener()); listeners.add(new LoggingApplicationListener()); listeners.add(new RemoteUrlPropertyExtractor()); 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 969139b253..bb6ed9ee42 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -510,7 +510,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.ConfigFileEnvironmentPostProcessor + * @see org.springframework.boot.context.config.ConfigFileApplicationListener */ protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { environment.getActiveProfiles(); // ensure they are initialized diff --git a/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java index 403edf193a..0ba283556f 100644 --- a/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java @@ -27,7 +27,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.SpringApplication; -import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor; +import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.json.JsonParser; import org.springframework.boot.json.JsonParserFactory; @@ -99,8 +99,8 @@ public class CloudFoundryVcapEnvironmentPostProcessor private static final String VCAP_SERVICES = "VCAP_SERVICES"; - // Before ConfigFileEnvironmentPostProcessor so values there can use these ones - private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER - 1; + // Before ConfigFileApplicationListener so values there can use these ones + private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1; private final JsonParser parser = JsonParserFactory.getJsonParser(); 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 0fb73f3bb3..d82f1e2ef0 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,7 +20,6 @@ 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; @@ -52,9 +51,9 @@ public class AnsiOutputApplicationListener @Override public int getOrder() { - // Apply after the EnvironmentPostProcessingApplicationListener has called all + // Apply after ConfigFileApplicationListener has called all // EnvironmentPostProcessors - return EnvironmentPostProcessingApplicationListener.ORDER + 1; + return ConfigFileApplicationListener.DEFAULT_ORDER + 1; } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java similarity index 91% rename from spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessor.java rename to spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java index 6d6e169527..762b9219aa 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java @@ -34,11 +34,13 @@ 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; @@ -52,6 +54,7 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.Assert; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; @@ -90,8 +93,8 @@ import org.springframework.validation.BindException; * @author Stephane Nicoll * @author Andy Wilkinson */ -public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProcessor, - ApplicationListener, Ordered { +public class ConfigFileApplicationListener implements EnvironmentPostProcessor, + ApplicationListener, Ordered { private static final String DEFAULT_PROPERTIES = "defaultProperties"; @@ -135,6 +138,29 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces private final ConversionService conversionService = new DefaultConversionService(); + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ApplicationEnvironmentPreparedEvent) { + onApplicationEnvironmentPreparedEvent( + (ApplicationEnvironmentPreparedEvent) event); + } + if (event instanceof ApplicationPreparedEvent) { + onApplicationPreparedEvent(event); + } + } + + private void onApplicationEnvironmentPreparedEvent( + ApplicationEnvironmentPreparedEvent event) { + List postProcessors = SpringFactoriesLoader + .loadFactories(EnvironmentPostProcessor.class, + getClass().getClassLoader()); + postProcessors.add(this); + for (EnvironmentPostProcessor postProcessor : postProcessors) { + postProcessor.postProcessEnvironment(event.getEnvironment(), + event.getSpringApplication()); + } + } + @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { @@ -142,10 +168,9 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces bindToSpringApplication(environment, application); } - @Override - public void onApplicationEvent(ApplicationPreparedEvent event) { - this.logger.replayTo(ConfigFileEnvironmentPostProcessor.class); - addPostProcessors(event.getApplicationContext()); + private void onApplicationPreparedEvent(ApplicationEvent event) { + this.logger.replayTo(ConfigFileApplicationListener.class); + addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); } /** @@ -268,7 +293,7 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces */ private class Loader { - private final Log logger = ConfigFileEnvironmentPostProcessor.this.logger; + private final Log logger = ConfigFileApplicationListener.this.logger; private final ConfigurableEnvironment environment; @@ -417,18 +442,19 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces /** * Return the active profiles that have not been processed yet. - *

If a profile is enabled via both {@link #ACTIVE_PROFILES_PROPERTY} and + *

+ * If a profile is enabled via both {@link #ACTIVE_PROFILES_PROPERTY} and * {@link ConfigurableEnvironment#addActiveProfile(String)} it needs to be - * filtered so that the {@link #ACTIVE_PROFILES_PROPERTY} value takes - * precedence. - *

Concretely, if the "cloud" profile is enabled via the environment, - * it will take less precedence that any profile set via the - * {@link #ACTIVE_PROFILES_PROPERTY}. + * filtered so that the {@link #ACTIVE_PROFILES_PROPERTY} value takes precedence. + *

+ * Concretely, if the "cloud" profile is enabled via the environment, it will take + * less precedence that any profile set via the {@link #ACTIVE_PROFILES_PROPERTY}. * @param initialActiveProfiles the profiles that have been enabled via * {@link #ACTIVE_PROFILES_PROPERTY} * @return the additional profiles from the environment to enable */ - private List filterEnvironmentProfiles(Set initialActiveProfiles) { + private List filterEnvironmentProfiles( + Set initialActiveProfiles) { List additionalProfiles = new ArrayList(); for (String profile : this.environment.getActiveProfiles()) { if (!initialActiveProfiles.contains(profile)) { @@ -501,7 +527,7 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces } } locations.addAll( - asResolvedSet(ConfigFileEnvironmentPostProcessor.this.searchLocations, + asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; } @@ -511,8 +537,7 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), null); } - return asResolvedSet(ConfigFileEnvironmentPostProcessor.this.names, - DEFAULT_NAMES); + return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); } private Set asResolvedSet(String value, String fallback) { diff --git a/spring-boot/src/main/java/org/springframework/boot/context/config/package-info.java b/spring-boot/src/main/java/org/springframework/boot/context/config/package-info.java index cecc7bd0e8..73a6f25539 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/config/package-info.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/config/package-info.java @@ -18,6 +18,6 @@ * External configuration support allowing 'application.properties' to be loaded and used * within a Spring Boot application. * - * @see org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor + * @see org.springframework.boot.context.config.ConfigFileApplicationListener */ package org.springframework.boot.context.config; diff --git a/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessingApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessingApplicationListener.java deleted file mode 100644 index ffacd2beba..0000000000 --- a/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessingApplicationListener.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012-2015 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import java.util.List; - -import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.io.support.SpringFactoriesLoader; - -/** - * An {@link ApplicationListener} that responds to an - * {@link ApplicationEnvironmentPreparedEvent} and calls all - * {@link EnvironmentPostProcessor EnvironmentPostProcessors} that are available via - * {@code spring.factories}. - *

- * Post-processors are called in the order defined by - * {@link AnnotationAwareOrderComparator}. - * - * @author Andy Wilkinson - * @since 1.3.0 - * @see SpringFactoriesLoader#loadFactories(Class, ClassLoader) - */ -public class EnvironmentPostProcessingApplicationListener - implements ApplicationListener, Ordered { - - /** - * The order for the {@link EnvironmentPostProcessingApplicationListener}. - */ - public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 10; - - @Override - public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { - List postProcessors = SpringFactoriesLoader - .loadFactories(EnvironmentPostProcessor.class, - getClass().getClassLoader()); - for (EnvironmentPostProcessor postProcessor : postProcessors) { - postProcessor.postProcessEnvironment(event.getEnvironment(), - event.getSpringApplication()); - } - } - - @Override - public int getOrder() { - return ORDER; - } - -} diff --git a/spring-boot/src/main/java/org/springframework/boot/test/ConfigFileApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/test/ConfigFileApplicationContextInitializer.java index 6a2f7c0c9b..5c0e3fdc08 100644 --- a/spring-boot/src/main/java/org/springframework/boot/test/ConfigFileApplicationContextInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/test/ConfigFileApplicationContextInitializer.java @@ -16,7 +16,7 @@ package org.springframework.boot.test; -import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor; +import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.ContextConfiguration; @@ -27,14 +27,14 @@ import org.springframework.test.context.ContextConfiguration; * {@literal application.properties}. * * @author Phillip Webb - * @see ConfigFileEnvironmentPostProcessor + * @see ConfigFileApplicationListener */ public class ConfigFileApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(final ConfigurableApplicationContext applicationContext) { - new ConfigFileEnvironmentPostProcessor() { + new ConfigFileApplicationListener() { public void apply() { addPropertySources(applicationContext.getEnvironment(), applicationContext); diff --git a/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot/src/main/resources/META-INF/spring.factories index ee6f17a1ea..cf6e8ae70d 100644 --- a/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot/src/main/resources/META-INF/spring.factories @@ -19,14 +19,12 @@ org.springframework.context.ApplicationListener=\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ -org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor,\ +org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ -org.springframework.boot.env.EnvironmentPostProcessingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\ org.springframework.boot.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.logging.LoggingApplicationListener # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ -org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ -org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor +org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor diff --git a/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java similarity index 99% rename from spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessorTests.java rename to spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java index 5300afb0c1..4225dbe017 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileEnvironmentPostProcessorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java @@ -42,7 +42,7 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.Banner; import org.springframework.boot.SpringApplication; -import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor.ConfigurationPropertySources; +import org.springframework.boot.context.config.ConfigFileApplicationListener.ConfigurationPropertySources; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.env.EnumerableCompositePropertySource; import org.springframework.boot.test.EnvironmentTestUtils; @@ -74,18 +74,18 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** - * Tests for {@link ConfigFileEnvironmentPostProcessor}. + * Tests for {@link ConfigFileApplicationListener}. * * @author Phillip Webb * @author Dave Syer */ -public class ConfigFileEnvironmentPostProcessorTests { +public class ConfigFileApplicationListenerTests { private final StandardEnvironment environment = new StandardEnvironment(); private final SpringApplication application = new SpringApplication(); - private final ConfigFileEnvironmentPostProcessor initializer = new ConfigFileEnvironmentPostProcessor(); + private final ConfigFileApplicationListener initializer = new ConfigFileApplicationListener(); @Rule public ExpectedException expected = ExpectedException.none();