Java Spring上下文层次结构中bean的销毁顺序

Java Spring上下文层次结构中bean的销毁顺序,java,spring,spring-test,hierarchical,context-configuration,Java,Spring,Spring Test,Hierarchical,Context Configuration,当一个Spring上下文层次结构关闭时,没有保证bean被销毁的顺序,这是正确的吗?例如,子上下文中的bean将在父上下文之前被销毁。从一个极小的例子来看,上下文的破坏似乎在上下文之间完全不协调(奇怪的是)。这两个上下文都注册了一个关机钩子,稍后将在不同的线程中执行 @RunWith(SpringJUnit4ClassRunner.class) @ContextHierarchy({ @ContextConfiguration(classes = {ATest.Root.class}),

当一个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

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

我自己也在研究同一个问题,一切看起来都不再奇怪了。尽管我还是希望这能有不同的表现

当您有父&子Spring上下文时,父对子对象一无所知。这就是弹簧的设计方式,所有设置都是如此

现在可能有一些区别

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

当webapp(或容器)关闭时,
ContextLoaderListener
DispatcherServlet
通过
ServletContextListener.contextDestroyed(…)
Servlet.destroy()
接收通知

根据First servlet&过滤器被销毁,并且只有在它们完成之后才会被销毁

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

独立webapp 以下内容不仅适用于独立的weapps,也适用于任何利用层次上下文的独立Spring应用程序

由于没有容器,stanadlone应用程序需要与JVM本身通信,以便接收并处理关闭信号。这是使用一种机制来完成的

Spring不尝试推断它运行的环境,除了JVM能力\版本(但是Spring Boot可以在自动推断环境方面做得很好)

因此,要使Spring注册成为一个关闭钩子,您需要在创建上下文()时说出它。如果不这样做,就根本不会调用
@PreDestroy
/
DisposableBean
回调

在JVM中注册上下文的关闭钩子后,它将收到通知,并将正确处理该上下文的关闭

如果您有父子上下文,您可能需要为每个上下文
.registerShutdownHook()
。这在某些情况下有效。但是JVM以不确定的顺序调用关闭挂钩,所以不幸的是,这并不能真正解决主题问题

现在我们该怎么办呢

可能最简单(虽然不是最优雅)的解决方案是在父上下文中放置一个
ApplicationListener
DisposableBean
,并

  • 引用子上下文
  • 从父上下文中保留对子上下文(例如,db连接)至关重要的bean,以便在子上下文处于活动状态之前保留它们(这可以通过
    @Autowire
    @DependsOn
    或xml副本来完成)
  • 在关闭父上下文之前关闭子上下文
JUnit测试 最初的问题是JUnit测试。我真的没挖那么远,但我怀疑这一次的情况又不同了。因为是
SpringJUnit4ClassRunner
首先控制了它们和上下文层次结构。从另一方面来说,JUnit测试有一个定义良好、管理良好的生命周期(相当于列表servlet容器)


在理解了内部工作原理之后,我认为解决这个问题应该很容易:)

多亏了充分的解释,我才能够解决我的问题。非常感谢。