简体   繁体   English

Spring上下文层次结构中的bean破坏顺序

[英]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 现在可能会有一些区别

Webapp in servlet container servlet容器中的Webapp

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(或容器)时, ContextLoaderListenerDispatcherServlet都会相应地通过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上下文(它是一个子上下文),然后销毁根上下文。

Standalone webapp 独立的webapp

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放在父上下文中,

  • having reference[s] to child context[s] 有参考[s]到儿童背景[s]
  • holding beans from parent context which are critical for child context (eg db connection) so that they're kept until child context is alive (this can be done with @Autowire or @DependsOn or with xml counterparts) 从父上下文中保存对子上下文至关重要的bean(例如数据库连接),以便它们保持到子上下文存活(这可以通过@Autowire@DependsOn或xml对应的方式完成)
  • closing child context[s] before parent context is closed 在父上下文关闭之前关闭子上下文[s]

JUnit tests JUnit测试

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM