问题
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? E.g. the beans in the child context will be destroyed before the parent context. 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?
回答1:
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.
Now there may be some distinctions
Webapp in servlet container
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
.
When webapp (or container) is shut down both the ContextLoaderListener
and DispatcherServlet
receive notifications through ServletContextListener.contextDestroyed(...)
and Servlet.destroy()
correspondingly.
According to javadoc firstly servlet & filters are destroyed and only after they're done ServletContextListener
's are.
So in webapps running in servlet container a DispatcherServlet
context (which is child one) is destroyed first and only then root context is destroyed.
Standalone webapp
Following is equally true not only for standalone weapps but for any standalone Spring apps utilising hierarchical contexts.
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. This is done using shutdown hooks mechanism.
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).
So to make Spring register a shutdown hook you need to say it to do so when you create a context (javadoc). If you don't do that you won't have your @PreDestroy
/DisposableBean
callbacks invoked at all.
Once you register context's shutdown hook with JVM it will be notified and will handle the shutdown properly for that context.
If you have parent-child contexts you may want to .registerShutdownHook()
for each of them. 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.
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
- having reference[s] to child context[s]
- holding beans from parent context which are critical for child context (e.g. db connection) so that they're kept until child context is alive (this can be done with
@Autowire
or@DependsOn
or with xml counterparts) - closing child context[s] before parent context is closed
JUnit tests
The original question presented a JUnit test. 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. From the other hand JUnit tests has a well defined and managed lifecycle (pretty much list servlet container).
Any way having understood the inner workings I believe it should be easy for you solve this one :)
来源:https://stackoverflow.com/questions/35806663/order-of-bean-destruction-in-spring-context-hierarchy