Propogate startup failures to management context

Update EndpointWebMvcAutoConfiguration so that ApplicationFailedEvents
cause the management context to close.

Prior to this commit if an application failed to start (for example
because `server.port` was already in use) the management context would
remain open and the application would not exit.

Fixes gh-5388
This commit is contained in:
Phillip Webb 2016-05-16 17:26:03 -07:00
parent f0b6d346d7
commit 68983400fb
2 changed files with 46 additions and 9 deletions

View File

@ -54,9 +54,11 @@ import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.web.filter.ApplicationContextHeaderFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@ -172,7 +174,7 @@ public class EndpointWebMvcAutoConfiguration
EmbeddedServletContainerAutoConfiguration.class,
DispatcherServletAutoConfiguration.class);
registerEmbeddedServletContainerFactory(childContext);
CloseEventPropagationListener.addIfPossible(this.applicationContext,
CloseManagementContextListener.addIfPossible(this.applicationContext,
childContext);
childContext.refresh();
managementContextResolver().setApplicationContext(childContext);
@ -235,25 +237,42 @@ public class EndpointWebMvcAutoConfiguration
}
/**
* {@link ApplicationListener} to propagate the {@link ContextClosedEvent} from a
* parent to a child.
* {@link ApplicationListener} to propagate the {@link ContextClosedEvent} and
* {@link ApplicationFailedEvent} from a parent to a child.
*/
private static class CloseEventPropagationListener
implements ApplicationListener<ContextClosedEvent> {
private static class CloseManagementContextListener
implements ApplicationListener<ApplicationEvent> {
private final ApplicationContext parentContext;
private final ConfigurableApplicationContext childContext;
CloseEventPropagationListener(ApplicationContext parentContext,
CloseManagementContextListener(ApplicationContext parentContext,
ConfigurableApplicationContext childContext) {
this.parentContext = parentContext;
this.childContext = childContext;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (event.getApplicationContext() == this.parentContext) {
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent((ApplicationFailedEvent) event);
}
};
private void onContextClosedEvent(ContextClosedEvent event) {
propagateCloseIfNecessary(event.getApplicationContext());
}
private void onApplicationFailedEvent(ApplicationFailedEvent event) {
propagateCloseIfNecessary(event.getApplicationContext());
}
private void propagateCloseIfNecessary(ApplicationContext applicationContext) {
if (applicationContext == this.parentContext) {
this.childContext.close();
}
}
@ -268,7 +287,7 @@ public class EndpointWebMvcAutoConfiguration
private static void add(ConfigurableApplicationContext parentContext,
ConfigurableApplicationContext childContext) {
parentContext.addApplicationListener(
new CloseEventPropagationListener(parentContext, childContext));
new CloseManagementContextListener(parentContext, childContext));
}
}

View File

@ -62,10 +62,12 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.testutil.Matched;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -282,6 +284,22 @@ public class EndpointWebMvcAutoConfigurationTests {
assertContent("/endpoint", managementPort, "endpointoutput");
}
@Test
public void onDifferentPortWithPrimaryFailure() throws Exception {
this.applicationContext.register(RootConfig.class, EndpointConfig.class,
DifferentPortConfig.class, BaseConfiguration.class,
EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class);
this.applicationContext.refresh();
ApplicationContext managementContext = this.applicationContext
.getBean(ManagementContextResolver.class).getApplicationContext();
ApplicationFailedEvent event = mock(ApplicationFailedEvent.class);
given(event.getApplicationContext()).willReturn(this.applicationContext);
this.applicationContext.publishEvent(event);
assertThat(((ConfigurableApplicationContext) managementContext).isActive())
.isFalse();
this.applicationContext.close();
}
@Test
public void disabled() throws Exception {
this.applicationContext.register(RootConfig.class, EndpointConfig.class,