【问题标题】:Order of bean destruction in Spring context hierarchySpring上下文层次结构中的bean销毁顺序
【发布时间】:2016-06-18 19:33:09
【问题描述】:

说当 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

有没有办法强制以“正确”的顺序关闭上下文?

【问题讨论】:

    标签: java spring spring-test hierarchical context-configuration


    【解决方案1】:

    我自己也研究过同样的问题,一切看起来都不再奇怪了。虽然我仍然希望这有不同的表现。

    当你有父子 Spring 上下文时,父母对孩子一无所知。这就是 Spring 的设计方式,并且适用于所有设置。

    现在可能会有一些区别

    servlet 容器中的 Webapp

    这种情况下最常见的设置(不包括单上下文设置)是使用ContextLoaderListener 声明根上下文,并通过DispatcherServlet 声明子上下文。

    当 webapp(或容器)关闭时,ContextLoaderListenerDispatcherServlet 都会相应地通过 ServletContextListener.contextDestroyed(...)Servlet.destroy() 接收通知。

    根据javadoc,首先 servlet 和过滤器被销毁,只有在它们完成后ServletContextListener 才被销毁。

    因此,在 servlet 容器中运行的 webapps 中,首先销毁 DispatcherServlet 上下文(即子上下文),然后才销毁根上下文。

    独立网络应用

    以下内容同样适用,不仅适用于独立的 Weapp,而且适用于任何使用分层上下文的独立 Spring 应用。

    由于没有容器,因此独立应用程序需要与 JVM 本身进行通信,以便接收关闭信号并进行处理。这是使用shutdown hooks 机制完成的。

    Spring 不会尝试推断它运行的环境,除了 JVM 功能\版本(但 Spring Boot 可以很好地自动推断 env)。

    所以要让 Spring 注册一个关闭钩子,您需要在创建上下文 (javadoc) 时说明它。如果您不这样做,您将根本不会调用您的 @PreDestroy/DisposableBean 回调。

    一旦你向 JVM 注册了上下文的关闭钩子,它就会收到通知,并会针对该上下文正确处理关闭。

    如果您有父子上下文,您可能希望为每个上下文.registerShutdownHook()。这适用于某些情况。但是 JVM 以不确定的顺序调用关闭钩子,所以很遗憾,这并不能真正解决主题问题。

    现在我们该怎么办

    可能最简单(虽然不是最优雅)的解决方案是让ApplicationListener<ContextClosedEvent>DisposableBean 坐在父上下文中,并且

    • 具有对子上下文的引用[s]
    • 从父上下文中持有对子上下文(例如数据库连接)至关重要的 bean,以便它们一直保存到子上下文处于活动状态(这可以使用 @Autowire@DependsOn 或使用 xml 对应项来完成)李>
    • 在关闭父上下文之前关闭子上下文

    JUnit 测试

    最初的问题是一个 JUnit 测试。我并没有真正深入挖掘,但我怀疑这一次的情况又有所不同。因为它是SpringJUnit4ClassRunner,它首先统治着它们和上下文层次结构。另一方面,JUnit 测试具有良好定义和管理的生命周期(几乎是列表 servlet 容器)。

    了解内部工作原理的任何方式我相信你应该很容易解决这个问题:)

    【讨论】:

    • 感谢充分的解释,我能够解决我的问题。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2012-12-28
    • 1970-01-01
    • 1970-01-01
    • 2013-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-07
    相关资源
    最近更新 更多