Skip to content

Commit aa6dbdb

Browse files
committed
Ensure that listeners are called when application fails to run
Closes gh-9054
1 parent ad62905 commit aa6dbdb

File tree

2 files changed

+130
-3
lines changed

2 files changed

+130
-3
lines changed

spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.context.ConfigurableApplicationContext;
2727
import org.springframework.context.event.ApplicationEventMulticaster;
2828
import org.springframework.context.event.SimpleApplicationEventMulticaster;
29+
import org.springframework.context.support.AbstractApplicationContext;
2930
import org.springframework.core.Ordered;
3031
import org.springframework.core.env.ConfigurableEnvironment;
3132
import org.springframework.util.ErrorHandler;
@@ -94,12 +95,20 @@ public void contextLoaded(ConfigurableApplicationContext context) {
9495
@Override
9596
public void finished(ConfigurableApplicationContext context, Throwable exception) {
9697
SpringApplicationEvent event = getFinishedEvent(context, exception);
97-
if (context != null) {
98+
if (context != null && context.isActive()) {
9899
// Listeners have been registered to the application context so we should
99100
// use it at this point if we can
100101
context.publishEvent(event);
101102
}
102103
else {
104+
// An inactive context may not have a multicaster so we use our multicaster to
105+
// call all of the context's listeners instead
106+
if (context instanceof AbstractApplicationContext) {
107+
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
108+
.getApplicationListeners()) {
109+
this.initialMulticaster.addApplicationListener(listener);
110+
}
111+
}
103112
if (event instanceof ApplicationFailedEvent) {
104113
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
105114
}

spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,21 @@
3636
import org.junit.rules.ExpectedException;
3737

3838
import org.springframework.beans.BeansException;
39+
import org.springframework.beans.factory.BeanCreationException;
3940
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
4041
import org.springframework.beans.factory.support.BeanNameGenerator;
4142
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
4243
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
4344
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
4445
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
46+
import org.springframework.boot.context.event.ApplicationFailedEvent;
4547
import org.springframework.boot.context.event.ApplicationPreparedEvent;
4648
import org.springframework.boot.context.event.ApplicationReadyEvent;
4749
import org.springframework.boot.context.event.ApplicationStartingEvent;
4850
import org.springframework.boot.testutil.InternalOutputCapture;
4951
import org.springframework.context.ApplicationContext;
5052
import org.springframework.context.ApplicationContextAware;
53+
import org.springframework.context.ApplicationContextException;
5154
import org.springframework.context.ApplicationContextInitializer;
5255
import org.springframework.context.ApplicationEvent;
5356
import org.springframework.context.ApplicationListener;
@@ -722,11 +725,107 @@ public void onApplicationEvent(ApplicationEvent event) {
722725
private void verifyTestListenerEvents() {
723726
ApplicationListener<ApplicationEvent> listener = this.context
724727
.getBean("testApplicationListener", ApplicationListener.class);
725-
verify(listener).onApplicationEvent(argThat(isA(ContextRefreshedEvent.class)));
726-
verify(listener).onApplicationEvent(argThat(isA(ApplicationReadyEvent.class)));
728+
verifyListenerEvents(listener, ContextRefreshedEvent.class,
729+
ApplicationReadyEvent.class);
730+
}
731+
732+
@SuppressWarnings("unchecked")
733+
private void verifyListenerEvents(ApplicationListener<ApplicationEvent> listener,
734+
Class<? extends ApplicationEvent>... eventTypes) {
735+
for (Class<? extends ApplicationEvent> eventType : eventTypes) {
736+
verify(listener).onApplicationEvent(argThat(isA(eventType)));
737+
}
727738
verifyNoMoreInteractions(listener);
728739
}
729740

741+
@SuppressWarnings("unchecked")
742+
@Test
743+
public void applicationListenerFromApplicationIsCalledWhenContextFailsRefreshBeforeListenerRegistration() {
744+
ApplicationListener<ApplicationEvent> listener = mock(ApplicationListener.class);
745+
SpringApplication application = new SpringApplication(ExampleConfig.class);
746+
application.addListeners(listener);
747+
try {
748+
application.run();
749+
fail("Run should have failed with an ApplicationContextException");
750+
}
751+
catch (ApplicationContextException ex) {
752+
verifyListenerEvents(listener, ApplicationStartingEvent.class,
753+
ApplicationEnvironmentPreparedEvent.class,
754+
ApplicationPreparedEvent.class, ApplicationFailedEvent.class);
755+
}
756+
}
757+
758+
@SuppressWarnings("unchecked")
759+
@Test
760+
public void applicationListenerFromApplicationIsCalledWhenContextFailsRefreshAfterListenerRegistration() {
761+
ApplicationListener<ApplicationEvent> listener = mock(ApplicationListener.class);
762+
SpringApplication application = new SpringApplication(
763+
BrokenPostConstructConfig.class);
764+
application.setWebEnvironment(false);
765+
application.addListeners(listener);
766+
try {
767+
application.run();
768+
fail("Run should have failed with a BeanCreationException");
769+
}
770+
catch (BeanCreationException ex) {
771+
verifyListenerEvents(listener, ApplicationStartingEvent.class,
772+
ApplicationEnvironmentPreparedEvent.class,
773+
ApplicationPreparedEvent.class, ApplicationFailedEvent.class);
774+
}
775+
}
776+
777+
@SuppressWarnings("unchecked")
778+
@Test
779+
public void applicationListenerFromContextIsCalledWhenContextFailsRefreshBeforeListenerRegistration() {
780+
final ApplicationListener<ApplicationEvent> listener = mock(
781+
ApplicationListener.class);
782+
SpringApplication application = new SpringApplication(ExampleConfig.class);
783+
application.addInitializers(
784+
new ApplicationContextInitializer<ConfigurableApplicationContext>() {
785+
786+
@Override
787+
public void initialize(
788+
ConfigurableApplicationContext applicationContext) {
789+
applicationContext.addApplicationListener(listener);
790+
}
791+
792+
});
793+
try {
794+
application.run();
795+
fail("Run should have failed with an ApplicationContextException");
796+
}
797+
catch (ApplicationContextException ex) {
798+
verifyListenerEvents(listener, ApplicationFailedEvent.class);
799+
}
800+
}
801+
802+
@SuppressWarnings("unchecked")
803+
@Test
804+
public void applicationListenerFromContextIsCalledWhenContextFailsRefreshAfterListenerRegistration() {
805+
final ApplicationListener<ApplicationEvent> listener = mock(
806+
ApplicationListener.class);
807+
SpringApplication application = new SpringApplication(
808+
BrokenPostConstructConfig.class);
809+
application.setWebEnvironment(false);
810+
application.addInitializers(
811+
new ApplicationContextInitializer<ConfigurableApplicationContext>() {
812+
813+
@Override
814+
public void initialize(
815+
ConfigurableApplicationContext applicationContext) {
816+
applicationContext.addApplicationListener(listener);
817+
}
818+
819+
});
820+
try {
821+
application.run();
822+
fail("Run should have failed with a BeanCreationException");
823+
}
824+
catch (BeanCreationException ex) {
825+
verifyListenerEvents(listener, ApplicationFailedEvent.class);
826+
}
827+
}
828+
730829
@Test
731830
public void registerShutdownHookOff() throws Exception {
732831
SpringApplication application = new SpringApplication(ExampleConfig.class);
@@ -936,6 +1035,25 @@ static class ExampleConfig {
9361035

9371036
}
9381037

1038+
@Configuration
1039+
static class BrokenPostConstructConfig {
1040+
1041+
@Bean
1042+
public Thing thing() {
1043+
return new Thing();
1044+
}
1045+
1046+
static class Thing {
1047+
1048+
@PostConstruct
1049+
public void boom() {
1050+
throw new IllegalStateException();
1051+
}
1052+
1053+
}
1054+
1055+
}
1056+
9391057
@Configuration
9401058
static class ListenerConfig {
9411059

0 commit comments

Comments
 (0)