From aa6dbdbae253095ecbf722c2ef44e5f961c117e6 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 6 Jun 2017 14:53:05 +0100 Subject: [PATCH] Ensure that listeners are called when application fails to run Closes gh-9054 --- .../event/EventPublishingRunListener.java | 11 +- .../boot/SpringApplicationTests.java | 122 +++++++++++++++++- 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java b/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java index 695c819d92c..779f5263a4b 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java @@ -26,6 +26,7 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.ErrorHandler; @@ -94,12 +95,20 @@ public class EventPublishingRunListener implements SpringApplicationRunListener, @Override public void finished(ConfigurableApplicationContext context, Throwable exception) { SpringApplicationEvent event = getFinishedEvent(context, exception); - if (context != null) { + if (context != null && context.isActive()) { // Listeners have been registered to the application context so we should // use it at this point if we can context.publishEvent(event); } else { + // An inactive context may not have a multicaster so we use our multicaster to + // call all of the context's listeners instead + if (context instanceof AbstractApplicationContext) { + for (ApplicationListener listener : ((AbstractApplicationContext) context) + .getApplicationListeners()) { + this.initialMulticaster.addApplicationListener(listener); + } + } if (event instanceof ApplicationFailedEvent) { this.initialMulticaster.setErrorHandler(new LoggingErrorHandler()); } 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 4d9d0a4d6ba..b358a836ed2 100644 --- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -36,18 +36,21 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultBeanNameGenerator; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.boot.testutil.InternalOutputCapture; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; @@ -722,11 +725,107 @@ public class SpringApplicationTests { private void verifyTestListenerEvents() { ApplicationListener listener = this.context .getBean("testApplicationListener", ApplicationListener.class); - verify(listener).onApplicationEvent(argThat(isA(ContextRefreshedEvent.class))); - verify(listener).onApplicationEvent(argThat(isA(ApplicationReadyEvent.class))); + verifyListenerEvents(listener, ContextRefreshedEvent.class, + ApplicationReadyEvent.class); + } + + @SuppressWarnings("unchecked") + private void verifyListenerEvents(ApplicationListener listener, + Class... eventTypes) { + for (Class eventType : eventTypes) { + verify(listener).onApplicationEvent(argThat(isA(eventType))); + } verifyNoMoreInteractions(listener); } + @SuppressWarnings("unchecked") + @Test + public void applicationListenerFromApplicationIsCalledWhenContextFailsRefreshBeforeListenerRegistration() { + ApplicationListener listener = mock(ApplicationListener.class); + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.addListeners(listener); + try { + application.run(); + fail("Run should have failed with an ApplicationContextException"); + } + catch (ApplicationContextException ex) { + verifyListenerEvents(listener, ApplicationStartingEvent.class, + ApplicationEnvironmentPreparedEvent.class, + ApplicationPreparedEvent.class, ApplicationFailedEvent.class); + } + } + + @SuppressWarnings("unchecked") + @Test + public void applicationListenerFromApplicationIsCalledWhenContextFailsRefreshAfterListenerRegistration() { + ApplicationListener listener = mock(ApplicationListener.class); + SpringApplication application = new SpringApplication( + BrokenPostConstructConfig.class); + application.setWebEnvironment(false); + application.addListeners(listener); + try { + application.run(); + fail("Run should have failed with a BeanCreationException"); + } + catch (BeanCreationException ex) { + verifyListenerEvents(listener, ApplicationStartingEvent.class, + ApplicationEnvironmentPreparedEvent.class, + ApplicationPreparedEvent.class, ApplicationFailedEvent.class); + } + } + + @SuppressWarnings("unchecked") + @Test + public void applicationListenerFromContextIsCalledWhenContextFailsRefreshBeforeListenerRegistration() { + final ApplicationListener listener = mock( + ApplicationListener.class); + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.addInitializers( + new ApplicationContextInitializer() { + + @Override + public void initialize( + ConfigurableApplicationContext applicationContext) { + applicationContext.addApplicationListener(listener); + } + + }); + try { + application.run(); + fail("Run should have failed with an ApplicationContextException"); + } + catch (ApplicationContextException ex) { + verifyListenerEvents(listener, ApplicationFailedEvent.class); + } + } + + @SuppressWarnings("unchecked") + @Test + public void applicationListenerFromContextIsCalledWhenContextFailsRefreshAfterListenerRegistration() { + final ApplicationListener listener = mock( + ApplicationListener.class); + SpringApplication application = new SpringApplication( + BrokenPostConstructConfig.class); + application.setWebEnvironment(false); + application.addInitializers( + new ApplicationContextInitializer() { + + @Override + public void initialize( + ConfigurableApplicationContext applicationContext) { + applicationContext.addApplicationListener(listener); + } + + }); + try { + application.run(); + fail("Run should have failed with a BeanCreationException"); + } + catch (BeanCreationException ex) { + verifyListenerEvents(listener, ApplicationFailedEvent.class); + } + } + @Test public void registerShutdownHookOff() throws Exception { SpringApplication application = new SpringApplication(ExampleConfig.class); @@ -936,6 +1035,25 @@ public class SpringApplicationTests { } + @Configuration + static class BrokenPostConstructConfig { + + @Bean + public Thing thing() { + return new Thing(); + } + + static class Thing { + + @PostConstruct + public void boom() { + throw new IllegalStateException(); + } + + } + + } + @Configuration static class ListenerConfig {