From 285dd5b270ecb955ebbb8490de2f4be6bdac10dd Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 21 Nov 2013 09:35:00 +0000 Subject: [PATCH] ApplicationContextInitializers now listen for ContextRefreshedEvent The AutoConfigurationReportLoggingInitializer wasn't working in non-GenericApplicationContext becasue teh BeanFatcory wasn't available for registering its listener during initialization. Instead of relying on that rather fragile state I decided to give any ApplicationContextInitializer that was itself an ApplicationListener an explicit callback with a ContextRefreshedEvent, and move that interface up a level in the logging initializer. Works much better. --- ...ConfigurationReportLoggingInitializer.java | 158 ++++++++---------- ...gurationReportLoggingInitializerTests.java | 28 +++- .../boot/SpringApplication.java | 19 ++- .../boot/SpringApplicationTests.java | 26 +++ 4 files changed, 141 insertions(+), 90 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializer.java index 0a7f7cb146a..a4056a86639 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializer.java @@ -20,7 +20,6 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplicationErrorHandler; import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcome; @@ -30,6 +29,7 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -49,18 +49,21 @@ import org.springframework.util.StringUtils; */ public class AutoConfigurationReportLoggingInitializer implements ApplicationContextInitializer, - SpringApplicationErrorHandler { + SpringApplicationErrorHandler, ApplicationListener { - private static final String LOGGER_BEAN = "autoConfigurationReportLogger"; + private final Log logger = LogFactory.getLog(getClass()); - private AutoConfigurationReportLogger loggerBean; + private ConfigurableApplicationContext applicationContext; + + private AutoConfigurationReport report; @Override public void initialize(ConfigurableApplicationContext applicationContext) { - this.loggerBean = new AutoConfigurationReportLogger(applicationContext); - ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); - if (!beanFactory.containsSingleton(LOGGER_BEAN)) { - beanFactory.registerSingleton(LOGGER_BEAN, this.loggerBean); + this.applicationContext = applicationContext; + if (applicationContext instanceof GenericApplicationContext) { + // Get the report early in case the context fails to load + this.report = AutoConfigurationReport.get(this.applicationContext + .getBeanFactory()); } } @@ -68,99 +71,80 @@ public class AutoConfigurationReportLoggingInitializer implements public void handleError(SpringApplication application, ConfigurableApplicationContext applicationContext, String[] args, Throwable exception) { - if (this.loggerBean != null) { - this.loggerBean.logAutoConfigurationReport(true); + logAutoConfigurationReport(true); + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + if (event.getApplicationContext() == this.applicationContext) { + logAutoConfigurationReport(); } } - /** - * Spring bean to actually perform the logging. - */ - public static class AutoConfigurationReportLogger implements - ApplicationListener { + private void logAutoConfigurationReport() { + logAutoConfigurationReport(!this.applicationContext.isActive()); + } - private final Log logger = LogFactory.getLog(getClass()); - - private final ConfigurableApplicationContext applicationContext; - - private final AutoConfigurationReport report; - - public AutoConfigurationReportLogger( - ConfigurableApplicationContext applicationContext) { - this.applicationContext = applicationContext; - // Get the report early in case the context fails to load + void logAutoConfigurationReport(boolean isCrashReport) { + if (this.report == null) { this.report = AutoConfigurationReport.get(this.applicationContext .getBeanFactory()); - } - - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - if (event.getApplicationContext() == this.applicationContext) { - logAutoConfigurationReport(); + if (this.report.getConditionAndOutcomesBySource().size() > 0) { + if (isCrashReport && this.logger.isInfoEnabled()) { + this.logger.info(getLogMessage(this.report + .getConditionAndOutcomesBySource())); + } + else if (!isCrashReport && this.logger.isDebugEnabled()) { + this.logger.debug(getLogMessage(this.report + .getConditionAndOutcomesBySource())); } } + } - private void logAutoConfigurationReport() { - logAutoConfigurationReport(!this.applicationContext.isActive()); - } - - void logAutoConfigurationReport(boolean isCrashReport) { - if (this.report.getConditionAndOutcomesBySource().size() > 0) { - if (isCrashReport && this.logger.isInfoEnabled()) { - this.logger.info(getLogMessage(this.report - .getConditionAndOutcomesBySource())); - } - else if (!isCrashReport && this.logger.isDebugEnabled()) { - this.logger.debug(getLogMessage(this.report - .getConditionAndOutcomesBySource())); - } + private StringBuilder getLogMessage(Map outcomes) { + StringBuilder message = new StringBuilder(); + message.append("\n\n\n"); + message.append("=========================\n"); + message.append("AUTO-CONFIGURATION REPORT\n"); + message.append("=========================\n\n\n"); + message.append("Positive matches:\n"); + message.append("-----------------\n"); + for (Map.Entry entry : outcomes.entrySet()) { + if (entry.getValue().isFullMatch()) { + addLogMessage(message, entry.getKey(), entry.getValue()); } } - - private StringBuilder getLogMessage(Map outcomes) { - StringBuilder message = new StringBuilder(); - message.append("\n\n\n"); - message.append("=========================\n"); - message.append("AUTO-CONFIGURATION REPORT\n"); - message.append("=========================\n\n\n"); - message.append("Positive matches:\n"); - message.append("-----------------\n"); - for (Map.Entry entry : outcomes.entrySet()) { - if (entry.getValue().isFullMatch()) { - addLogMessage(message, entry.getKey(), entry.getValue()); - } - } - message.append("\n\n"); - message.append("Negative matches:\n"); - message.append("-----------------\n"); - for (Map.Entry entry : outcomes.entrySet()) { - if (!entry.getValue().isFullMatch()) { - addLogMessage(message, entry.getKey(), entry.getValue()); - } - } - message.append("\n\n"); - return message; - } - - private void addLogMessage(StringBuilder message, String source, - ConditionAndOutcomes conditionAndOutcomes) { - message.append("\n " + ClassUtils.getShortName(source) + "\n"); - for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) { - message.append(" - "); - if (StringUtils.hasLength(conditionAndOutcome.getOutcome().getMessage())) { - message.append(conditionAndOutcome.getOutcome().getMessage()); - } - else { - message.append(conditionAndOutcome.getOutcome().isMatch() ? "matched" - : "did not match"); - } - message.append(" ("); - message.append(ClassUtils.getShortName(conditionAndOutcome.getCondition() - .getClass())); - message.append(")\n"); + message.append("\n\n"); + message.append("Negative matches:\n"); + message.append("-----------------\n"); + for (Map.Entry entry : outcomes.entrySet()) { + if (!entry.getValue().isFullMatch()) { + addLogMessage(message, entry.getKey(), entry.getValue()); } } + message.append("\n\n"); + return message; + } + + private void addLogMessage(StringBuilder message, String source, + ConditionAndOutcomes conditionAndOutcomes) { + message.append("\n " + ClassUtils.getShortName(source) + "\n"); + for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) { + message.append(" - "); + if (StringUtils.hasLength(conditionAndOutcome.getOutcome().getMessage())) { + message.append(conditionAndOutcome.getOutcome().getMessage()); + } + else { + message.append(conditionAndOutcome.getOutcome().isMatch() ? "matched" + : "did not match"); + } + message.append(" ("); + message.append(ClassUtils.getShortName(conditionAndOutcome.getCondition() + .getClass())); + message.append(")\n"); + } + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializerTests.java index 1184df4c1e9..8d599b87142 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReportLoggingInitializerTests.java @@ -29,16 +29,19 @@ import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import org.springframework.boot.autoconfigure.AutoConfigurationReportLoggingInitializer.AutoConfigurationReportLogger; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.BDDMockito.given; @@ -104,6 +107,7 @@ public class AutoConfigurationReportLoggingInitializerTests { this.initializer.initialize(context); context.register(Config.class); context.refresh(); + this.initializer.onApplicationEvent(new ContextRefreshedEvent(context)); assertThat(this.debugLog.size(), not(equalTo(0))); } @@ -130,6 +134,7 @@ public class AutoConfigurationReportLoggingInitializerTests { this.initializer.initialize(context); context.register(Config.class); context.refresh(); + this.initializer.onApplicationEvent(new ContextRefreshedEvent(context)); for (String message : this.debugLog) { System.out.println(message); } @@ -138,10 +143,29 @@ public class AutoConfigurationReportLoggingInitializerTests { assertThat(l, containsString("not a web application (OnWebApplicationCondition)")); } + @Test + public void canBeUsedInApplicationContext() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + new AutoConfigurationReportLoggingInitializer().initialize(context); + context.refresh(); + assertNotNull(context.getBean(AutoConfigurationReport.class)); + } + + @Test + public void canBeUsedInNonGenericApplicationContext() throws Exception { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(Config.class); + new AutoConfigurationReportLoggingInitializer().initialize(context); + context.refresh(); + assertNotNull(context.getBean(AutoConfigurationReport.class)); + } + public static class MockLogFactory extends LogFactoryImpl { @Override public Log getInstance(String name) throws LogConfigurationException { - if (AutoConfigurationReportLogger.class.getName().equals(name)) { + if (AutoConfigurationReportLoggingInitializer.class.getName().equals(name)) { return logThreadLocal.get(); } return new NoOpLog(); 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 26ab455528d..0203f039ca3 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -36,12 +36,16 @@ import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.GenericTypeResolver; @@ -298,7 +302,7 @@ public class SpringApplication { getApplicationLog(), stopWatch); } - runCommandLineRunners(context, args); + afterRefresh(context, args); return context; } catch (RuntimeException ex) { @@ -312,6 +316,19 @@ public class SpringApplication { } + private void afterRefresh(ConfigurableApplicationContext context, String[] args) { + ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(); + List> initializers = new ArrayList>( + getInitializers()); + for (ApplicationContextInitializer initializer : initializers) { + if (initializer instanceof ApplicationListener) { + multicaster.addApplicationListener((ApplicationListener) initializer); + } + } + multicaster.multicastEvent(new ContextRefreshedEvent(context)); + runCommandLineRunners(context, args); + } + private void handleError(ConfigurableApplicationContext context, String[] args, Throwable exception) { List> initializers = new ArrayList>( diff --git a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 5c9a06df246..650254f1431 100644 --- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -33,11 +33,13 @@ import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletConta import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.env.CommandLinePropertySource; @@ -156,6 +158,30 @@ public class SpringApplicationTests { assertThat(getEnvironment().getProperty("foo"), equalTo("bar")); } + @Test + public void contextRefreshedEventListener() throws Exception { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setWebEnvironment(false); + final AtomicReference reference = new AtomicReference(); + class InitalizerListener implements + ApplicationContextInitializer, + ApplicationListener { + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + reference.set(event.getApplicationContext()); + } + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + } + } + application.setInitializers(Arrays.asList(new InitalizerListener())); + this.context = application.run("--foo=bar"); + assertThat(this.context, sameInstance(reference.get())); + // Custom initializers do not switch off the defaults + assertThat(getEnvironment().getProperty("foo"), equalTo("bar")); + } + @Test public void defaultApplicationContext() throws Exception { SpringApplication application = new SpringApplication(ExampleConfig.class);