Spring 上下文层次结构中的 bean 销毁顺序
Order of bean destruction in Spring context hierarchy
当 Spring 上下文层次结构关闭时,无法保证销毁 bean 的顺序是否正确?例如。子上下文中的 bean 将在父上下文之前被销毁。从一个最小的例子来看,上下文的破坏似乎在上下文之间完全不协调(很奇怪)。两个上下文都注册了一个关闭挂钩,稍后将在不同的线程中执行。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
@ContextConfiguration(classes = {ATest.Root.class}),
@ContextConfiguration(classes = {ATest.Child.class})
})
public class ATest {
@Test
public void contextTest() {
}
public static class Root {
@Bean
Foo foo() {
return new Foo();
}
}
public static class Child {
@Bean
Bar bar() {
return new Bar();
}
}
static class Foo {
Logger logger = LoggerFactory.getLogger(Foo.class);
volatile boolean destroyed;
@PostConstruct
void setup() {
logger.info("foo setup");
}
@PreDestroy
void destroy() {
destroyed = true;
logger.info("foo destroy");
}
}
static class Bar {
@Autowired
Foo foo;
Logger logger = LoggerFactory.getLogger(Bar.class);
@PostConstruct
void setup() {
logger.info("bar setup with foo = {}", foo);
}
@PreDestroy
void destroy() {
logger.info("bar destroy, foo is destroyed={}", foo.destroyed);
}
}
}
给出输出:
21:38:53.287 [Test worker] INFO ATest$Foo - foo setup
21:38:53.327 [Test worker] INFO ATest$Bar - bar setup with foo = com.tango.citrine.spring.ATest$Foo@2458117b
21:38:53.363 [Thread-4] INFO ATest$Foo - foo destroy
21:38:53.364 [Thread-5] INFO ATest$Bar - bar destroy, foo is destroyed=true
有什么方法可以强制按 "correct" 顺序关闭上下文?
我自己也研究过同样的问题,一切看起来都不再奇怪了。虽然我仍然希望它的表现有所不同。
当你有 parent & child Spring 上下文时 parent 对 child 一无所知。这就是 Spring 的设计方式,所有设置都是如此。
现在可能会有一些区别
servlet 容器中的 Webapp
这种情况下最常见的设置(不包括 single-context 设置)是通过 DispatcherServlet
声明根上下文 ContextLoaderListener
和 child 上下文。
当 webapp(或容器)关闭时,ContextLoaderListener
和 DispatcherServlet
会相应地通过 ServletContextListener.contextDestroyed(...)
和 Servlet.destroy()
接收通知。
根据 javadoc 首先销毁 servlet 和过滤器,只有在它们完成后 ServletContextListener
才会被销毁。
所以在 webapps 运行 中,在 servlet 容器中,首先销毁 DispatcherServlet
上下文(这是 child 一个),然后才销毁根上下文。
独立网络应用程序
以下内容不仅适用于独立的 weapps,也适用于任何使用分层上下文的独立 Spring 应用程序。
由于没有容器,因此 stanadlone 应用程序需要与 JVM 本身通信以接收关闭信号并进行处理。这是使用 shutdown hooks 机制完成的。
Spring 不会尝试推断它在 运行 中的环境,除了 JVM capabilities\version(但是 Spring Boot 可以很好地自动推断环境).
因此,要使 Spring 注册一个关闭挂钩,您需要在创建上下文 (javadoc) 时声明它这样做。如果你不这样做,你根本不会调用 @PreDestroy
/DisposableBean
回调。
一旦您向 JVM 注册上下文的关闭挂钩,它将收到通知并正确处理关闭该上下文。
如果您有 parent-child 个上下文,您可能希望为每个上下文 .registerShutdownHook()
。这将适用于某些情况。但是 JVM 按 non-deterministic 顺序调用关闭挂钩,所以不幸的是,这并没有真正解决主题问题。
现在我们能怎么办呢
可能最简单(虽然不是最优雅)的解决方案是让 ApplicationListener<ContextClosedEvent>
或 DisposableBean
位于 parent 上下文中并且
- 参考[s] child 上下文[s]
- 从 parent 上下文中持有对 child 上下文至关重要的 bean(例如数据库连接),以便它们一直保留到 child 上下文处于活动状态(这可以通过
@Autowire
或 @DependsOn
或与 xml 对应)
- 在 parent 上下文关闭之前关闭 child 上下文[s]
JUnit 测试
原题是JUnit测试。我真的没有深入挖掘,但我怀疑这一次的情况又有所不同。因为它是 SpringJUnit4ClassRunner
规则它们,首先是上下文层次结构。另一方面,JUnit 测试有一个明确定义和管理的生命周期(几乎是列表 servlet 容器)。
任何了解内部工作原理的方式我相信你应该很容易解决这个问题:)
当 Spring 上下文层次结构关闭时,无法保证销毁 bean 的顺序是否正确?例如。子上下文中的 bean 将在父上下文之前被销毁。从一个最小的例子来看,上下文的破坏似乎在上下文之间完全不协调(很奇怪)。两个上下文都注册了一个关闭挂钩,稍后将在不同的线程中执行。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
@ContextConfiguration(classes = {ATest.Root.class}),
@ContextConfiguration(classes = {ATest.Child.class})
})
public class ATest {
@Test
public void contextTest() {
}
public static class Root {
@Bean
Foo foo() {
return new Foo();
}
}
public static class Child {
@Bean
Bar bar() {
return new Bar();
}
}
static class Foo {
Logger logger = LoggerFactory.getLogger(Foo.class);
volatile boolean destroyed;
@PostConstruct
void setup() {
logger.info("foo setup");
}
@PreDestroy
void destroy() {
destroyed = true;
logger.info("foo destroy");
}
}
static class Bar {
@Autowired
Foo foo;
Logger logger = LoggerFactory.getLogger(Bar.class);
@PostConstruct
void setup() {
logger.info("bar setup with foo = {}", foo);
}
@PreDestroy
void destroy() {
logger.info("bar destroy, foo is destroyed={}", foo.destroyed);
}
}
}
给出输出:
21:38:53.287 [Test worker] INFO ATest$Foo - foo setup
21:38:53.327 [Test worker] INFO ATest$Bar - bar setup with foo = com.tango.citrine.spring.ATest$Foo@2458117b
21:38:53.363 [Thread-4] INFO ATest$Foo - foo destroy
21:38:53.364 [Thread-5] INFO ATest$Bar - bar destroy, foo is destroyed=true
有什么方法可以强制按 "correct" 顺序关闭上下文?
我自己也研究过同样的问题,一切看起来都不再奇怪了。虽然我仍然希望它的表现有所不同。
当你有 parent & child Spring 上下文时 parent 对 child 一无所知。这就是 Spring 的设计方式,所有设置都是如此。
现在可能会有一些区别
servlet 容器中的 Webapp
这种情况下最常见的设置(不包括 single-context 设置)是通过 DispatcherServlet
声明根上下文 ContextLoaderListener
和 child 上下文。
当 webapp(或容器)关闭时,ContextLoaderListener
和 DispatcherServlet
会相应地通过 ServletContextListener.contextDestroyed(...)
和 Servlet.destroy()
接收通知。
根据 javadoc 首先销毁 servlet 和过滤器,只有在它们完成后 ServletContextListener
才会被销毁。
所以在 webapps 运行 中,在 servlet 容器中,首先销毁 DispatcherServlet
上下文(这是 child 一个),然后才销毁根上下文。
独立网络应用程序
以下内容不仅适用于独立的 weapps,也适用于任何使用分层上下文的独立 Spring 应用程序。
由于没有容器,因此 stanadlone 应用程序需要与 JVM 本身通信以接收关闭信号并进行处理。这是使用 shutdown hooks 机制完成的。
Spring 不会尝试推断它在 运行 中的环境,除了 JVM capabilities\version(但是 Spring Boot 可以很好地自动推断环境).
因此,要使 Spring 注册一个关闭挂钩,您需要在创建上下文 (javadoc) 时声明它这样做。如果你不这样做,你根本不会调用 @PreDestroy
/DisposableBean
回调。
一旦您向 JVM 注册上下文的关闭挂钩,它将收到通知并正确处理关闭该上下文。
如果您有 parent-child 个上下文,您可能希望为每个上下文 .registerShutdownHook()
。这将适用于某些情况。但是 JVM 按 non-deterministic 顺序调用关闭挂钩,所以不幸的是,这并没有真正解决主题问题。
现在我们能怎么办呢
可能最简单(虽然不是最优雅)的解决方案是让 ApplicationListener<ContextClosedEvent>
或 DisposableBean
位于 parent 上下文中并且
- 参考[s] child 上下文[s]
- 从 parent 上下文中持有对 child 上下文至关重要的 bean(例如数据库连接),以便它们一直保留到 child 上下文处于活动状态(这可以通过
@Autowire
或@DependsOn
或与 xml 对应) - 在 parent 上下文关闭之前关闭 child 上下文[s]
JUnit 测试
原题是JUnit测试。我真的没有深入挖掘,但我怀疑这一次的情况又有所不同。因为它是 SpringJUnit4ClassRunner
规则它们,首先是上下文层次结构。另一方面,JUnit 测试有一个明确定义和管理的生命周期(几乎是列表 servlet 容器)。
任何了解内部工作原理的方式我相信你应该很容易解决这个问题:)