diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java index 5f9c35f1..10a2dbd0 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java @@ -19,10 +19,13 @@ package org.springframework.cloud.bootstrap; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.boot.Banner.Mode; @@ -31,6 +34,7 @@ import org.springframework.boot.WebApplicationType; import org.springframework.boot.builder.ParentContextApplicationContextInitializer; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.logging.LoggingApplicationListener; import org.springframework.cloud.bootstrap.encrypt.EnvironmentDecryptApplicationInitializer; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; @@ -44,9 +48,9 @@ import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; +import org.springframework.core.env.PropertySource.StubPropertySource; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.SystemEnvironmentPropertySource; -import org.springframework.core.env.PropertySource.StubPropertySource; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -165,6 +169,14 @@ public class BootstrapApplicationListener // Don't use the default properties in this builder .registerShutdownHook(false).logStartupInfo(false) .web(WebApplicationType.NONE); + if (environment.getPropertySources().contains("refreshArgs")) { + // If we are doing a context refresh, really we only want to refresh the + // Environment, and there are some toxic listeners (like the + // LoggingApplicationListener) that affect global static state, so we need a + // way to switch those off. + builder.application() + .setListeners(filterListeners(builder.application().getListeners())); + } List> sources = new ArrayList<>(); for (String name : names) { Class cls = ClassUtils.resolveClassName(name, null); @@ -180,7 +192,8 @@ public class BootstrapApplicationListener builder.sources(sources.toArray(new Class[sources.size()])); final ConfigurableApplicationContext context = builder.run(); // gh-214 using spring.application.name=bootstrap to set the context id via - // `ContextIdApplicationContextInitializer` prevents apps from getting the actual spring.application.name + // `ContextIdApplicationContextInitializer` prevents apps from getting the actual + // spring.application.name // during the bootstrap phase. context.setId("bootstrap"); // Make the bootstrap context a parent of the app context @@ -192,6 +205,18 @@ public class BootstrapApplicationListener return context; } + private Collection> filterListeners( + Set> listeners) { + Set> result = new LinkedHashSet<>(); + for (ApplicationListener listener : listeners) { + if (!(listener instanceof LoggingApplicationListener) + && !(listener instanceof LoggingSystemShutdownListener)) { + result.add(listener); + } + } + return result; + } + private void mergeDefaultProperties(MutablePropertySources environment, MutablePropertySources bootstrap) { String name = DEFAULT_PROPERTIES; diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherTests.java index 0fd34599..53eba101 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherTests.java @@ -1,16 +1,17 @@ package org.springframework.cloud.context.refresh; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.After; import org.junit.Test; import org.mockito.Mockito; + import org.springframework.boot.SpringApplication; +import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.test.util.TestPropertyValues.Type; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; @@ -22,10 +23,18 @@ import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; +import static org.assertj.core.api.Assertions.assertThat; + public class ContextRefresherTests { private RefreshScope scope = Mockito.mock(RefreshScope.class); + @After + public void close() { + System.clearProperty(LoggingSystem.SYSTEM_PROPERTY); + TestLoggingSystem.count = 0; + } + @Test public void orderNewPropertiesConsistentWithNewContext() { try (ConfigurableApplicationContext context = SpringApplication.run(Empty.class, @@ -38,8 +47,8 @@ public class ContextRefresherTests { ContextRefresher refresher = new ContextRefresher(context, scope); refresher.refresh(); names = names(context.getEnvironment().getPropertySources()); - assertThat(names) - .contains("applicationConfig: [classpath:/bootstrap-refresh.properties]"); + assertThat(names).contains( + "applicationConfig: [classpath:/bootstrap-refresh.properties]"); assertThat(names).containsSequence( "applicationConfig: [classpath:/application.properties]", "applicationConfig: [classpath:/bootstrap-refresh.properties]", @@ -53,7 +62,8 @@ public class ContextRefresherTests { // a bootstrapProperties immediately try (ConfigurableApplicationContext context = SpringApplication.run(Empty.class, "--spring.main.webEnvironment=false", "--debug=false", - "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh")) { + "--spring.main.bannerMode=OFF", + "--spring.cloud.bootstrap.name=refresh")) { List names = names(context.getEnvironment().getPropertySources()); System.err.println("***** " + context.getEnvironment().getPropertySources()); assertThat(names).doesNotContain("bootstrapProperties"); @@ -71,19 +81,42 @@ public class ContextRefresherTests { public void parentContextIsClosed() { // Use spring.cloud.bootstrap.name to switch off the defaults (which would pick up // a bootstrapProperties immediately - try (ConfigurableApplicationContext context = SpringApplication.run(ContextRefresherTests.class, - "--spring.main.webEnvironment=false", "--debug=false", - "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh")) { + try (ConfigurableApplicationContext context = SpringApplication.run( + ContextRefresherTests.class, "--spring.main.webEnvironment=false", + "--debug=false", "--spring.main.bannerMode=OFF", + "--spring.cloud.bootstrap.name=refresh")) { ContextRefresher refresher = new ContextRefresher(context, scope); TestPropertyValues.of( "spring.cloud.bootstrap.sources: org.springframework.cloud.context.refresh.ContextRefresherTests.PropertySourceConfiguration") .applyTo(context); - ConfigurableApplicationContext refresherContext = refresher.addConfigFilesToEnvironment(); - assertThat(refresherContext.getParent()).isNotNull().isInstanceOf(ConfigurableApplicationContext.class); - ConfigurableApplicationContext parent = (ConfigurableApplicationContext) refresherContext.getParent(); + ConfigurableApplicationContext refresherContext = refresher + .addConfigFilesToEnvironment(); + assertThat(refresherContext.getParent()).isNotNull() + .isInstanceOf(ConfigurableApplicationContext.class); + ConfigurableApplicationContext parent = (ConfigurableApplicationContext) refresherContext + .getParent(); assertThat(parent.isActive()).isFalse(); } } + + @Test + public void loggingSystemNotInitialized() { + System.setProperty(LoggingSystem.SYSTEM_PROPERTY, + TestLoggingSystem.class.getName()); + TestLoggingSystem system = (TestLoggingSystem) LoggingSystem + .get(getClass().getClassLoader()); + assertThat(system.getCount()).isEqualTo(0); + try (ConfigurableApplicationContext context = SpringApplication.run(Empty.class, + "--spring.main.webEnvironment=false", "--debug=false", + "--spring.main.bannerMode=OFF", + "--spring.cloud.bootstrap.name=refresh")) { + assertThat(system.getCount()).isEqualTo(4); + ContextRefresher refresher = new ContextRefresher(context, scope); + refresher.refresh(); + assertThat(system.getCount()).isEqualTo(4); + } + } + private List names(MutablePropertySources propertySources) { List list = new ArrayList<>(); for (PropertySource p : propertySources) { @@ -95,7 +128,7 @@ public class ContextRefresherTests { @Configuration protected static class Empty { } - + @Configuration // This is added to bootstrap context as a source in bootstrap.properties protected static class PropertySourceConfiguration implements PropertySourceLocator { @@ -110,4 +143,22 @@ public class ContextRefresherTests { } + public static class TestLoggingSystem extends LoggingSystem { + + private static int count; + + public TestLoggingSystem(ClassLoader loader) { + } + + public int getCount() { + return count; + } + + @Override + public void beforeInitialize() { + count++; + } + + } + }