4

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

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

4

1 回答 1

4

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

当你有父子 Spring 上下文时,父母对孩子一无所知。这就是 Spring 的设计方式,所有设置都是如此。

现在可能有一些区别

servlet 容器中的 Webapp

这种情况下最常见的设置(不包括单上下文设置)是ContextLoaderListener通过DispatcherServlet.

当 webapp(或容器)关闭时,并ContextLoaderListener相应地DispatcherServlet接收通知。ServletContextListener.contextDestroyed(...)Servlet.destroy()

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

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

独立的网络应用程序

以下不仅适用于独立的 Weapp,也适用于任何使用分层上下文的独立 Spring 应用。

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

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 容器)。

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

于 2016-12-13T11:02:31.053 回答