Commit 285dd5b2 authored by Dave Syer's avatar Dave Syer

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.
parent f3a225f3
...@@ -20,7 +20,6 @@ import java.util.Map; ...@@ -20,7 +20,6 @@ import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationErrorHandler; import org.springframework.boot.SpringApplicationErrorHandler;
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcome; import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcome;
...@@ -30,6 +29,7 @@ import org.springframework.context.ApplicationContextInitializer; ...@@ -30,6 +29,7 @@ import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -49,18 +49,21 @@ import org.springframework.util.StringUtils; ...@@ -49,18 +49,21 @@ import org.springframework.util.StringUtils;
*/ */
public class AutoConfigurationReportLoggingInitializer implements public class AutoConfigurationReportLoggingInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, ApplicationContextInitializer<ConfigurableApplicationContext>,
SpringApplicationErrorHandler { SpringApplicationErrorHandler, ApplicationListener<ContextRefreshedEvent> {
private static final String LOGGER_BEAN = "autoConfigurationReportLogger"; private final Log logger = LogFactory.getLog(getClass());
private AutoConfigurationReportLogger loggerBean; private ConfigurableApplicationContext applicationContext;
private AutoConfigurationReport report;
@Override @Override
public void initialize(ConfigurableApplicationContext applicationContext) { public void initialize(ConfigurableApplicationContext applicationContext) {
this.loggerBean = new AutoConfigurationReportLogger(applicationContext); this.applicationContext = applicationContext;
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); if (applicationContext instanceof GenericApplicationContext) {
if (!beanFactory.containsSingleton(LOGGER_BEAN)) { // Get the report early in case the context fails to load
beanFactory.registerSingleton(LOGGER_BEAN, this.loggerBean); this.report = AutoConfigurationReport.get(this.applicationContext
.getBeanFactory());
} }
} }
...@@ -68,99 +71,80 @@ public class AutoConfigurationReportLoggingInitializer implements ...@@ -68,99 +71,80 @@ public class AutoConfigurationReportLoggingInitializer implements
public void handleError(SpringApplication application, public void handleError(SpringApplication application,
ConfigurableApplicationContext applicationContext, String[] args, ConfigurableApplicationContext applicationContext, String[] args,
Throwable exception) { Throwable exception) {
if (this.loggerBean != null) { logAutoConfigurationReport(true);
this.loggerBean.logAutoConfigurationReport(true);
}
} }
/** @Override
* Spring bean to actually perform the logging. public void onApplicationEvent(ContextRefreshedEvent event) {
*/ if (event.getApplicationContext() == this.applicationContext) {
public static class AutoConfigurationReportLogger implements logAutoConfigurationReport();
ApplicationListener<ContextRefreshedEvent> { }
}
private final Log logger = LogFactory.getLog(getClass());
private final ConfigurableApplicationContext applicationContext;
private final AutoConfigurationReport report; private void logAutoConfigurationReport() {
logAutoConfigurationReport(!this.applicationContext.isActive());
}
public AutoConfigurationReportLogger( void logAutoConfigurationReport(boolean isCrashReport) {
ConfigurableApplicationContext applicationContext) { if (this.report == null) {
this.applicationContext = applicationContext;
// Get the report early in case the context fails to load
this.report = AutoConfigurationReport.get(this.applicationContext this.report = AutoConfigurationReport.get(this.applicationContext
.getBeanFactory()); .getBeanFactory());
} }
if (this.report.getConditionAndOutcomesBySource().size() > 0) {
@Override if (isCrashReport && this.logger.isInfoEnabled()) {
public void onApplicationEvent(ContextRefreshedEvent event) { this.logger.info(getLogMessage(this.report
if (event.getApplicationContext() == this.applicationContext) { .getConditionAndOutcomesBySource()));
logAutoConfigurationReport(); }
else if (!isCrashReport && this.logger.isDebugEnabled()) {
this.logger.debug(getLogMessage(this.report
.getConditionAndOutcomesBySource()));
} }
} }
}
private void logAutoConfigurationReport() { private StringBuilder getLogMessage(Map<String, ConditionAndOutcomes> outcomes) {
logAutoConfigurationReport(!this.applicationContext.isActive()); 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<String, ConditionAndOutcomes> entry : outcomes.entrySet()) {
if (entry.getValue().isFullMatch()) {
addLogMessage(message, entry.getKey(), entry.getValue());
}
} }
message.append("\n\n");
void logAutoConfigurationReport(boolean isCrashReport) { message.append("Negative matches:\n");
if (this.report.getConditionAndOutcomesBySource().size() > 0) { message.append("-----------------\n");
if (isCrashReport && this.logger.isInfoEnabled()) { for (Map.Entry<String, ConditionAndOutcomes> entry : outcomes.entrySet()) {
this.logger.info(getLogMessage(this.report if (!entry.getValue().isFullMatch()) {
.getConditionAndOutcomesBySource())); addLogMessage(message, entry.getKey(), entry.getValue());
}
else if (!isCrashReport && this.logger.isDebugEnabled()) {
this.logger.debug(getLogMessage(this.report
.getConditionAndOutcomesBySource()));
}
} }
} }
message.append("\n\n");
return message;
}
private StringBuilder getLogMessage(Map<String, ConditionAndOutcomes> outcomes) { private void addLogMessage(StringBuilder message, String source,
StringBuilder message = new StringBuilder(); ConditionAndOutcomes conditionAndOutcomes) {
message.append("\n\n\n"); message.append("\n " + ClassUtils.getShortName(source) + "\n");
message.append("=========================\n"); for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
message.append("AUTO-CONFIGURATION REPORT\n"); message.append(" - ");
message.append("=========================\n\n\n"); if (StringUtils.hasLength(conditionAndOutcome.getOutcome().getMessage())) {
message.append("Positive matches:\n"); message.append(conditionAndOutcome.getOutcome().getMessage());
message.append("-----------------\n");
for (Map.Entry<String, ConditionAndOutcomes> entry : outcomes.entrySet()) {
if (entry.getValue().isFullMatch()) {
addLogMessage(message, entry.getKey(), entry.getValue());
}
} }
message.append("\n\n"); else {
message.append("Negative matches:\n"); message.append(conditionAndOutcome.getOutcome().isMatch() ? "matched"
message.append("-----------------\n"); : "did not match");
for (Map.Entry<String, ConditionAndOutcomes> entry : outcomes.entrySet()) {
if (!entry.getValue().isFullMatch()) {
addLogMessage(message, entry.getKey(), entry.getValue());
}
} }
message.append("\n\n"); message.append(" (");
return message; message.append(ClassUtils.getShortName(conditionAndOutcome.getCondition()
.getClass()));
message.append(")\n");
} }
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");
}
}
} }
} }
...@@ -29,16 +29,19 @@ import org.junit.Before; ...@@ -29,16 +29,19 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.springframework.boot.autoconfigure.AutoConfigurationReportLoggingInitializer.AutoConfigurationReportLogger;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; 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.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
...@@ -104,6 +107,7 @@ public class AutoConfigurationReportLoggingInitializerTests { ...@@ -104,6 +107,7 @@ public class AutoConfigurationReportLoggingInitializerTests {
this.initializer.initialize(context); this.initializer.initialize(context);
context.register(Config.class); context.register(Config.class);
context.refresh(); context.refresh();
this.initializer.onApplicationEvent(new ContextRefreshedEvent(context));
assertThat(this.debugLog.size(), not(equalTo(0))); assertThat(this.debugLog.size(), not(equalTo(0)));
} }
...@@ -130,6 +134,7 @@ public class AutoConfigurationReportLoggingInitializerTests { ...@@ -130,6 +134,7 @@ public class AutoConfigurationReportLoggingInitializerTests {
this.initializer.initialize(context); this.initializer.initialize(context);
context.register(Config.class); context.register(Config.class);
context.refresh(); context.refresh();
this.initializer.onApplicationEvent(new ContextRefreshedEvent(context));
for (String message : this.debugLog) { for (String message : this.debugLog) {
System.out.println(message); System.out.println(message);
} }
...@@ -138,10 +143,29 @@ public class AutoConfigurationReportLoggingInitializerTests { ...@@ -138,10 +143,29 @@ public class AutoConfigurationReportLoggingInitializerTests {
assertThat(l, containsString("not a web application (OnWebApplicationCondition)")); 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 { public static class MockLogFactory extends LogFactoryImpl {
@Override @Override
public Log getInstance(String name) throws LogConfigurationException { public Log getInstance(String name) throws LogConfigurationException {
if (AutoConfigurationReportLogger.class.getName().equals(name)) { if (AutoConfigurationReportLoggingInitializer.class.getName().equals(name)) {
return logThreadLocal.get(); return logThreadLocal.get();
} }
return new NoOpLog(); return new NoOpLog();
......
...@@ -36,12 +36,16 @@ import org.springframework.beans.factory.support.BeanNameGenerator; ...@@ -36,12 +36,16 @@ import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; 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.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
...@@ -298,7 +302,7 @@ public class SpringApplication { ...@@ -298,7 +302,7 @@ public class SpringApplication {
getApplicationLog(), stopWatch); getApplicationLog(), stopWatch);
} }
runCommandLineRunners(context, args); afterRefresh(context, args);
return context; return context;
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
...@@ -312,6 +316,19 @@ public class SpringApplication { ...@@ -312,6 +316,19 @@ public class SpringApplication {
} }
private void afterRefresh(ConfigurableApplicationContext context, String[] args) {
ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>(
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, private void handleError(ConfigurableApplicationContext context, String[] args,
Throwable exception) { Throwable exception) {
List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>( List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>(
......
...@@ -33,11 +33,13 @@ import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletConta ...@@ -33,11 +33,13 @@ import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletConta
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.StaticApplicationContext; import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource; import org.springframework.core.env.CommandLinePropertySource;
...@@ -156,6 +158,30 @@ public class SpringApplicationTests { ...@@ -156,6 +158,30 @@ public class SpringApplicationTests {
assertThat(getEnvironment().getProperty("foo"), equalTo("bar")); assertThat(getEnvironment().getProperty("foo"), equalTo("bar"));
} }
@Test
public void contextRefreshedEventListener() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
final AtomicReference<ApplicationContext> reference = new AtomicReference<ApplicationContext>();
class InitalizerListener implements
ApplicationContextInitializer<ConfigurableApplicationContext>,
ApplicationListener<ContextRefreshedEvent> {
@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 @Test
public void defaultApplicationContext() throws Exception { public void defaultApplicationContext() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class); SpringApplication application = new SpringApplication(ExampleConfig.class);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment