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.
This commit is contained in:
Dave Syer 2013-11-21 09:35:00 +00:00
parent f3a225f35f
commit 285dd5b270
4 changed files with 141 additions and 90 deletions

View File

@ -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<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
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<ContextRefreshedEvent> {
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<String, ConditionAndOutcomes> 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<String, ConditionAndOutcomes> entry : outcomes.entrySet()) {
if (entry.getValue().isFullMatch()) {
addLogMessage(message, entry.getKey(), entry.getValue());
}
}
private StringBuilder getLogMessage(Map<String, ConditionAndOutcomes> 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<String, ConditionAndOutcomes> 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<String, ConditionAndOutcomes> 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<String, ConditionAndOutcomes> 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");
}
}
}

View File

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

View File

@ -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<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,
Throwable exception) {
List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>(

View File

@ -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<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
public void defaultApplicationContext() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);