[英]Order of bean destruction in Spring context hierarchy
Is it correct to say that when a Spring context hierarchy is closed, there is no guaranteed order in which the beans will be destroyed? 说当Spring上下文层次结构关闭时,没有保证bean被销毁的顺序是正确的吗? Eg the beans in the child context will be destroyed before the parent context.
例如,子上下文中的bean将在父上下文之前被销毁。 From a minimal example the destruction of the contexts seems to be totally uncoordinated between the contexts (oddly enough).
从最小的例子来看,上下文的破坏似乎在上下文之间完全不协调(奇怪的是)。 Both contexts registers a shutdown hook which later will be executed in different threads.
两个上下文都注册了一个关闭钩子,后来将在不同的线程中执行。
@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);
}
}
}
Gives the output: 给出输出:
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
Is there any way to force the contexts to be closed in the "correct" order? 有没有办法强制以“正确”的顺序关闭上下文?
I just have dug in same issue myself and all doesn't look weird any more. 我自己也在同一个问题上挖过,所有这些看起来都不再怪异。 Though I still wished this behaves differently.
虽然我仍然希望这种行为有所不同。
When you have parent & child Spring contexts parent knows nothing about the child. 当你有父母和孩子的春天上下文时,父母对孩子一无所知。 This is how Spring is designed and this is true for all the setups.
这就是Spring的设计方式,这适用于所有设置。
Now there may be some distinctions 现在可能会有一些区别
The most common setup for this case (not counting single-context setups) is declaring root context with ContextLoaderListener
and child context by means of DispatcherServlet
. 这种情况下最常见的设置(不计算单上下文设置)是通过
DispatcherServlet
使用ContextLoaderListener
和子上下文声明根上下文。
When webapp (or container) is shut down both the ContextLoaderListener
and DispatcherServlet
receive notifications through ServletContextListener.contextDestroyed(...)
and Servlet.destroy()
correspondingly. 当关闭webapp(或容器)时,
ContextLoaderListener
和DispatcherServlet
都会相应地通过ServletContextListener.contextDestroyed(...)
和Servlet.destroy()
接收通知。
According to javadoc firstly servlet & filters are destroyed and only after they're done ServletContextListener
's are. 根据javadoc,首先servlet和过滤器被破坏,并且只有在它们完成
ServletContextListener
之后才会被破坏。
So in webapps running in servlet container a DispatcherServlet
context (which is child one) is destroyed first and only then root context is destroyed. 因此,在servlet容器中运行的webapps中,首先销毁
DispatcherServlet
上下文(它是一个子上下文),然后销毁根上下文。
Following is equally true not only for standalone weapps but for any standalone Spring apps utilising hierarchical contexts. 以下内容不仅适用于独立的weapps,也适用于任何使用分层上下文的独立Spring应用程序。
Since there is no container then the stanadlone app needs to communicate with the JVM itself in order to receive shutdown signal and handle it. 由于没有容器,因此stanadlone应用程序需要与JVM本身通信才能接收关闭信号并处理它。 This is done using shutdown hooks mechanism.
这是使用shutdown hooks机制完成的。
Spring doesn't try to deduce the environment it's running within except for JVM capabilities\\version (but Spring Boot can do a great job in deducing env automatically). 除了JVM功能\\版本之外,Spring并没有尝试推断它所运行的环境(但是Spring Boot可以在自动推导env方面做得很好)。
So to make Spring register a shutdown hook you need to say it to do so when you create a context ( javadoc ). 因此,要使Spring注册一个关闭钩子,您需要在创建上下文( javadoc )时这样做。 If you don't do that you won't have your
@PreDestroy
/ DisposableBean
callbacks invoked at all. 如果不这样做,则根本不会调用
@PreDestroy
/ DisposableBean
回调。
Once you register context's shutdown hook with JVM it will be notified and will handle the shutdown properly for that context . 一旦您向JVM注册了上下文的关闭挂钩,它将被通知并将正确处理该上下文的关闭。
If you have parent-child contexts you may want to .registerShutdownHook()
for each of them. 如果您有父子环境,则可能需要为每个子环境设置
.registerShutdownHook()
。 This will work for some cases. 这适用于某些情况。 But JVM invokes shutdown hooks in non-deterministic order, so this doesn't really solve the topic problem, unfortunately.
但是JVM以非确定性顺序调用了关闭钩子,所以不幸的是,这并没有真正解决主题问题。
Now what could we about that 那我们能做些什么呢?
Probably the easiest (though not the most elegant) solution would be to have an ApplicationListener<ContextClosedEvent>
or DisposableBean
sitting in parent context and 可能最简单(尽管不是最优雅)的解决方案是将
ApplicationListener<ContextClosedEvent>
或DisposableBean
放在父上下文中,
@Autowire
or @DependsOn
or with xml counterparts) @Autowire
或@DependsOn
或xml对应的方式完成) The original question presented a JUnit test. 最初的问题提出了一个JUnit测试。 I didn't dig that far really, but I suspect that things are again different with this one.
我真的没有那么深入,但我怀疑这个问题再次发生了变化。 Since it is the
SpringJUnit4ClassRunner
which rules them all and the context hierarchies first of all. 因为它是
SpringJUnit4ClassRunner
,它首先统治它们以及上下文层次结构。 From the other hand JUnit tests has a well defined and managed lifecycle (pretty much list servlet container). 另一方面,JUnit测试具有良好定义和管理的生命周期(几乎是列表servlet容器)。
Any way having understood the inner workings I believe it should be easy for you solve this one :) 任何理解内部运作的方式我相信你应该很容易解决这个问题:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.