Listen for parent close events and close child context

If the context hierarchy is from a SpringApplication we can control
the shutdown semantics a bit. Specifically we need a listener in the child context
that will shut it down when the parent closes (since assummably the child relies
on beans in the arent that may now be disposed).

Fixes gh-275
pull/276/merge
Dave Syer 11 years ago
parent ea1a8d0dc0
commit 2d54b54d81

@ -0,0 +1,90 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link ShutdownEndpoint}.
*
* @author Dave Syer
*/
public class ShutdownParentEndpointTests {
private ConfigurableApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void shutdownChild() throws Exception {
this.context = new SpringApplicationBuilder(Config.class).child(Empty.class)
.web(false).run();
assertThat((String) getEndpointBean().invoke().get("message"),
startsWith("Shutting down"));
assertTrue(this.context.isActive());
Thread.sleep(600);
assertFalse(this.context.isActive());
}
@Test
public void shutdownParent() throws Exception {
this.context = new SpringApplicationBuilder(Empty.class).child(Config.class)
.web(false).run();
assertThat((String) getEndpointBean().invoke().get("message"),
startsWith("Shutting down"));
assertTrue(this.context.isActive());
Thread.sleep(600);
assertFalse(this.context.isActive());
}
private ShutdownEndpoint getEndpointBean() {
return this.context.getBean(ShutdownEndpoint.class);
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public ShutdownEndpoint endpoint() {
ShutdownEndpoint endpoint = new ShutdownEndpoint();
endpoint.setEnabled(true);
return endpoint;
}
}
@Configuration
public static class Empty {
}
}

@ -18,16 +18,22 @@ package org.springframework.boot.context.initializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
/**
* {@link ApplicationContextInitializer} for setting the parent context.
* {@link ApplicationContextInitializer} for setting the parent context. Also publishes
* {@link ParentContextAvailableEvent} when the context is refreshed to signal to other
* listeners that the context is available and has a parent.
*
* @author Dave Syer
*/
public class ParentContextApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
ApplicationContextInitializer<ConfigurableApplicationContext>,
ApplicationListener<ContextRefreshedEvent>, Ordered {
private int order = Integer.MIN_VALUE;
@ -46,9 +52,31 @@ public class ParentContextApplicationContextInitializer implements
return this.order;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableApplicationContext) {
context.publishEvent(new ParentContextAvailableEvent(
(ConfigurableApplicationContext) context));
}
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setParent(this.parent);
}
public static class ParentContextAvailableEvent extends ApplicationEvent {
public ParentContextAvailableEvent(
ConfigurableApplicationContext applicationContext) {
super(applicationContext);
}
public ConfigurableApplicationContext getApplicationContext() {
return (ConfigurableApplicationContext) getSource();
}
}
}

@ -0,0 +1,74 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.listener;
import org.springframework.boot.context.initializer.ParentContextApplicationContextInitializer.ParentContextAvailableEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.Ordered;
/**
* Listener that closes the application context if its parent is closed. It listens for
* refresh events and grabs the current context from there, and then listens for closed
* events and propagates it down the hierarchy.
*
* @author Dave Syer
*/
public class ParentContextCloserListener implements
ApplicationListener<ParentContextAvailableEvent>, Ordered {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
@Override
public void onApplicationEvent(ParentContextAvailableEvent event) {
maybeInstallListenerInParent(event.getApplicationContext());
}
private void maybeInstallListenerInParent(ConfigurableApplicationContext child) {
if (child.getParent() instanceof ConfigurableApplicationContext) {
ConfigurableApplicationContext parent = (ConfigurableApplicationContext) child
.getParent();
parent.addApplicationListener(new ContextCloserListener(child));
}
}
protected static class ContextCloserListener implements
ApplicationListener<ContextClosedEvent> {
private ConfigurableApplicationContext context;
public ContextCloserListener(ConfigurableApplicationContext context) {
this.context = context;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (this.context != null
&& event.getApplicationContext() == this.context.getParent()
&& this.context.isActive()) {
this.context.close();
}
}
}
}

@ -11,5 +11,6 @@ org.springframework.boot.context.listener.EnvironmentDelegateApplicationListener
org.springframework.boot.context.listener.FileEncodingApplicationListener,\
org.springframework.boot.context.listener.LoggingApplicationListener,\
org.springframework.boot.context.listener.VcapApplicationListener,\
org.springframework.boot.context.listener.ParentContextCloserListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorInitializer

Loading…
Cancel
Save