Java Spring上下文层次结构中bean的销毁顺序
当一个Spring上下文层次结构关闭时,没有保证bean被销毁的顺序,这是正确的吗?例如,子上下文中的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}),
@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
或xml副本来完成)@DependsOn
- 在关闭父上下文之前关闭子上下文
SpringJUnit4ClassRunner
首先控制了它们和上下文层次结构。从另一方面来说,JUnit测试有一个定义良好、管理良好的生命周期(相当于列表servlet容器)
在理解了内部工作原理之后,我认为解决这个问题应该很容易:)多亏了充分的解释,我才能够解决我的问题。非常感谢。