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
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user