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 66ea0ed8db..1cd7c390e2 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; @@ -93,12 +94,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 c4bfd85237..83e7e48236 100644 --- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -41,10 +41,12 @@ import reactor.core.publisher.Mono; import org.springframework.beans.BeansException; import org.springframework.beans.CachedIntrospectionResults; +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.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; @@ -56,6 +58,7 @@ import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicatio import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; 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; @@ -790,11 +793,92 @@ public class SpringApplicationTests { private void verifyTestListenerEvents() { ApplicationListener listener = this.context .getBean("testApplicationListener", ApplicationListener.class); - verify(listener).onApplicationEvent(isA(ContextRefreshedEvent.class)); - verify(listener).onApplicationEvent(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(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.setWebApplicationType(WebApplicationType.NONE); + 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((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() { + ApplicationListener listener = mock(ApplicationListener.class); + SpringApplication application = new SpringApplication( + BrokenPostConstructConfig.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addInitializers((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); @@ -1017,6 +1101,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 {