Search code examples
javaspringspring-testhierarchicalcontext-configuration

Order of bean destruction in Spring context hierarchy


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?


Solution

  • 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 :)