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.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<ApplicationListener<?>> getListeners() {
List<ApplicationListener<?>> listeners = new ArrayList<ApplicationListener<?>>();
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());

@ -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

@ -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();

@ -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;
}
}

@ -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<ApplicationPreparedEvent>, Ordered {
public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
ApplicationListener<ApplicationEvent>, 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<EnvironmentPostProcessor> 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.
* <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
* filtered so that the {@link #ACTIVE_PROFILES_PROPERTY} value takes
* precedence.
* <p>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.
* <p>
* 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<String> filterEnvironmentProfiles(Set<String> initialActiveProfiles) {
private List<String> filterEnvironmentProfiles(
Set<String> initialActiveProfiles) {
List<String> additionalProfiles = new ArrayList<String>();
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<String> asResolvedSet(String value, String fallback) {

@ -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;

@ -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;
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<ConfigurableApplicationContext> {
@Override
public void initialize(final ConfigurableApplicationContext applicationContext) {
new ConfigFileEnvironmentPostProcessor() {
new ConfigFileApplicationListener() {
public void apply() {
addPropertySources(applicationContext.getEnvironment(),
applicationContext);

@ -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

@ -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();
Loading…
Cancel
Save