Drive EnvironmentPostProcessors from ConfigFileApplicationListener

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
pull/4334/head
Andy Wilkinson 9 years ago
parent 0adf037410
commit 833aac2b26

@ -24,12 +24,11 @@ import org.springframework.boot.Banner;
import org.springframework.boot.ResourceBanner; import org.springframework.boot.ResourceBanner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.AnsiOutputApplicationListener; 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.remote.client.RemoteClientConfiguration;
import org.springframework.boot.devtools.restart.RestartInitializer; import org.springframework.boot.devtools.restart.RestartInitializer;
import org.springframework.boot.devtools.restart.RestartScopeInitializer; import org.springframework.boot.devtools.restart.RestartScopeInitializer;
import org.springframework.boot.devtools.restart.Restarter; import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.env.EnvironmentPostProcessingApplicationListener;
import org.springframework.boot.logging.ClasspathLoggingApplicationListener; import org.springframework.boot.logging.ClasspathLoggingApplicationListener;
import org.springframework.boot.logging.LoggingApplicationListener; import org.springframework.boot.logging.LoggingApplicationListener;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
@ -73,8 +72,7 @@ public final class RemoteSpringApplication {
private Collection<ApplicationListener<?>> getListeners() { private Collection<ApplicationListener<?>> getListeners() {
List<ApplicationListener<?>> listeners = new ArrayList<ApplicationListener<?>>(); List<ApplicationListener<?>> listeners = new ArrayList<ApplicationListener<?>>();
listeners.add(new AnsiOutputApplicationListener()); listeners.add(new AnsiOutputApplicationListener());
listeners.add(new ConfigFileEnvironmentPostProcessor()); listeners.add(new ConfigFileApplicationListener());
listeners.add(new EnvironmentPostProcessingApplicationListener());
listeners.add(new ClasspathLoggingApplicationListener()); listeners.add(new ClasspathLoggingApplicationListener());
listeners.add(new LoggingApplicationListener()); listeners.add(new LoggingApplicationListener());
listeners.add(new RemoteUrlPropertyExtractor()); listeners.add(new RemoteUrlPropertyExtractor());

@ -510,7 +510,7 @@ public class SpringApplication {
* @param environment this application's environment * @param environment this application's environment
* @param args arguments passed to the {@code run} method * @param args arguments passed to the {@code run} method
* @see #configureEnvironment(ConfigurableEnvironment, String[]) * @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) { protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
environment.getActiveProfiles(); // ensure they are initialized environment.getActiveProfiles(); // ensure they are initialized

@ -27,7 +27,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication; 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.env.EnvironmentPostProcessor;
import org.springframework.boot.json.JsonParser; import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory; import org.springframework.boot.json.JsonParserFactory;
@ -99,8 +99,8 @@ public class CloudFoundryVcapEnvironmentPostProcessor
private static final String VCAP_SERVICES = "VCAP_SERVICES"; private static final String VCAP_SERVICES = "VCAP_SERVICES";
// Before ConfigFileEnvironmentPostProcessor so values there can use these ones // Before ConfigFileApplicationListener so values there can use these ones
private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER - 1; private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1;
private final JsonParser parser = JsonParserFactory.getJsonParser(); private final JsonParser parser = JsonParserFactory.getJsonParser();

@ -20,7 +20,6 @@ import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiOutput.Enabled; import org.springframework.boot.ansi.AnsiOutput.Enabled;
import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.EnvironmentPostProcessingApplicationListener;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -52,9 +51,9 @@ public class AnsiOutputApplicationListener
@Override @Override
public int getOrder() { public int getOrder() {
// Apply after the EnvironmentPostProcessingApplicationListener has called all // Apply after ConfigFileApplicationListener has called all
// EnvironmentPostProcessors // EnvironmentPostProcessors
return EnvironmentPostProcessingApplicationListener.ORDER + 1; return ConfigFileApplicationListener.DEFAULT_ORDER + 1;
} }
} }

@ -34,11 +34,13 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnumerableCompositePropertySource; import org.springframework.boot.env.EnumerableCompositePropertySource;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.PropertySourcesLoader; import org.springframework.boot.env.PropertySourcesLoader;
import org.springframework.boot.logging.DeferredLog; import org.springframework.boot.logging.DeferredLog;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor; 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.DefaultResourceLoader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -90,8 +93,8 @@ import org.springframework.validation.BindException;
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProcessor, public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
ApplicationListener<ApplicationPreparedEvent>, Ordered { ApplicationListener<ApplicationEvent>, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties"; private static final String DEFAULT_PROPERTIES = "defaultProperties";
@ -135,6 +138,29 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces
private final ConversionService conversionService = new DefaultConversionService(); 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<EnvironmentPostProcessor> postProcessors = SpringFactoriesLoader
.loadFactories(EnvironmentPostProcessor.class,
getClass().getClassLoader());
postProcessors.add(this);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
@Override @Override
public void postProcessEnvironment(ConfigurableEnvironment environment, public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) { SpringApplication application) {
@ -142,10 +168,9 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces
bindToSpringApplication(environment, application); bindToSpringApplication(environment, application);
} }
@Override private void onApplicationPreparedEvent(ApplicationEvent event) {
public void onApplicationEvent(ApplicationPreparedEvent event) { this.logger.replayTo(ConfigFileApplicationListener.class);
this.logger.replayTo(ConfigFileEnvironmentPostProcessor.class); addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
addPostProcessors(event.getApplicationContext());
} }
/** /**
@ -268,7 +293,7 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces
*/ */
private class Loader { private class Loader {
private final Log logger = ConfigFileEnvironmentPostProcessor.this.logger; private final Log logger = ConfigFileApplicationListener.this.logger;
private final ConfigurableEnvironment environment; private final ConfigurableEnvironment environment;
@ -417,18 +442,19 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces
/** /**
* Return the active profiles that have not been processed yet. * Return the active profiles that have not been processed yet.
* <p>If a profile is enabled via both {@link #ACTIVE_PROFILES_PROPERTY} and * <p>
* If a profile is enabled via both {@link #ACTIVE_PROFILES_PROPERTY} and
* {@link ConfigurableEnvironment#addActiveProfile(String)} it needs to be * {@link ConfigurableEnvironment#addActiveProfile(String)} it needs to be
* filtered so that the {@link #ACTIVE_PROFILES_PROPERTY} value takes * filtered so that the {@link #ACTIVE_PROFILES_PROPERTY} value takes precedence.
* precedence. * <p>
* <p>Concretely, if the "cloud" profile is enabled via the environment, * Concretely, if the "cloud" profile is enabled via the environment, it will take
* it will take less precedence that any profile set via the * less precedence that any profile set via the {@link #ACTIVE_PROFILES_PROPERTY}.
* {@link #ACTIVE_PROFILES_PROPERTY}.
* @param initialActiveProfiles the profiles that have been enabled via * @param initialActiveProfiles the profiles that have been enabled via
* {@link #ACTIVE_PROFILES_PROPERTY} * {@link #ACTIVE_PROFILES_PROPERTY}
* @return the additional profiles from the environment to enable * @return the additional profiles from the environment to enable
*/ */
private List<String> filterEnvironmentProfiles(Set<String> initialActiveProfiles) { private List<String> filterEnvironmentProfiles(
Set<String> initialActiveProfiles) {
List<String> additionalProfiles = new ArrayList<String>(); List<String> additionalProfiles = new ArrayList<String>();
for (String profile : this.environment.getActiveProfiles()) { for (String profile : this.environment.getActiveProfiles()) {
if (!initialActiveProfiles.contains(profile)) { if (!initialActiveProfiles.contains(profile)) {
@ -501,7 +527,7 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces
} }
} }
locations.addAll( locations.addAll(
asResolvedSet(ConfigFileEnvironmentPostProcessor.this.searchLocations, asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS)); DEFAULT_SEARCH_LOCATIONS));
return locations; return locations;
} }
@ -511,8 +537,7 @@ public class ConfigFileEnvironmentPostProcessor implements EnvironmentPostProces
return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
null); null);
} }
return asResolvedSet(ConfigFileEnvironmentPostProcessor.this.names, return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
DEFAULT_NAMES);
} }
private Set<String> asResolvedSet(String value, String fallback) { private Set<String> asResolvedSet(String value, String fallback) {

@ -18,6 +18,6 @@
* External configuration support allowing 'application.properties' to be loaded and used * External configuration support allowing 'application.properties' to be loaded and used
* within a Spring Boot application. * 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; package org.springframework.boot.context.config;

@ -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}.
* <p>
* 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<ApplicationEnvironmentPreparedEvent>, Ordered {
/**
* The order for the {@link EnvironmentPostProcessingApplicationListener}.
*/
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = SpringFactoriesLoader
.loadFactories(EnvironmentPostProcessor.class,
getClass().getClassLoader());
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
@Override
public int getOrder() {
return ORDER;
}
}

@ -16,7 +16,7 @@
package org.springframework.boot.test; 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.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
@ -27,14 +27,14 @@ import org.springframework.test.context.ContextConfiguration;
* {@literal application.properties}. * {@literal application.properties}.
* *
* @author Phillip Webb * @author Phillip Webb
* @see ConfigFileEnvironmentPostProcessor * @see ConfigFileApplicationListener
*/ */
public class ConfigFileApplicationContextInitializer public class ConfigFileApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> { implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override @Override
public void initialize(final ConfigurableApplicationContext applicationContext) { public void initialize(final ConfigurableApplicationContext applicationContext) {
new ConfigFileEnvironmentPostProcessor() { new ConfigFileApplicationListener() {
public void apply() { public void apply() {
addPropertySources(applicationContext.getEnvironment(), addPropertySources(applicationContext.getEnvironment(),
applicationContext); applicationContext);

@ -19,14 +19,12 @@ org.springframework.context.ApplicationListener=\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\ 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.context.config.DelegatingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener org.springframework.boot.logging.LoggingApplicationListener
# Environment Post Processors # Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor
org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor

@ -42,7 +42,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.boot.Banner; import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication; 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.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnumerableCompositePropertySource; import org.springframework.boot.env.EnumerableCompositePropertySource;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
@ -74,18 +74,18 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* Tests for {@link ConfigFileEnvironmentPostProcessor}. * Tests for {@link ConfigFileApplicationListener}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer * @author Dave Syer
*/ */
public class ConfigFileEnvironmentPostProcessorTests { public class ConfigFileApplicationListenerTests {
private final StandardEnvironment environment = new StandardEnvironment(); private final StandardEnvironment environment = new StandardEnvironment();
private final SpringApplication application = new SpringApplication(); private final SpringApplication application = new SpringApplication();
private final ConfigFileEnvironmentPostProcessor initializer = new ConfigFileEnvironmentPostProcessor(); private final ConfigFileApplicationListener initializer = new ConfigFileApplicationListener();
@Rule @Rule
public ExpectedException expected = ExpectedException.none(); public ExpectedException expected = ExpectedException.none();
Loading…
Cancel
Save