15

I have a Spring bean defined in beans.xml as follows:

<context:annotation-config />
[...]
<bean id="myBackend" class="mycompany.BackendBean" scope="singleton" />

Inside the bean are 2 methods, which must be executed at the start and before termination of the web application:

public class BackendBean implements IBackend {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(BackendBean.class);

    @PostConstruct
    public void init()
    {
        LOGGER.debug("init");
    }

    @PreDestroy
    public void destroy()
    {
        LOGGER.debug("destroy");
    }
}

When I run the server (mvn jetty:run), I can see the output of the init method in the console, from which I conclude that the init method is executed.

When I press Ctrl-C and Jetty starts to shut down, I don't see the output of the destroy method.

What should I change in order for the destroy method to be executed, when the application is terminated?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Glory to Russia
  • 17,289
  • 56
  • 182
  • 325

7 Answers7

18

For Spring to call @PreDestroy callback method when you application shuts down, you have to add a shutdown hook and close the application context it in. You could attach the hook to JVM using Runtime.getRuntime().addShutdownHook(Thread) or to Jetty if it provides such an API. Here is how you'd do it with JVM shutdown hook:

final ApplicationContext appContext = ... // create your application context 
                         // using one of the various application context classes
Runtime.getRuntime().addShutdownHook(new Thread() {
   public void run() {
       appContext.close();
   }});
Abhinav Sarkar
  • 23,534
  • 11
  • 81
  • 97
  • 18
    There is `appContext.registerShutdownHook()` for that. – Vitaly May 04 '13 at 11:00
  • Thanks. Where (in which method) should I put the shutdown hook registration? – Glory to Russia May 04 '13 at 12:42
  • 1
    @DmitriPisarenko The best place would be where you initialize your spring application context. – Abhinav Sarkar May 04 '13 at 14:00
  • @Vitaly Need to elaborate that registerShutDownHook() is a method of AbstractApplicationContext, not of ApplicationContext inteface. – troy Jun 17 '16 at 16:07
  • in case you re-deploy an application, java application doesnt shutdown and hence wont be called. So, use this if you are using spring-boot or you have disabled redeployment – Vinesh Nov 15 '16 at 14:17
  • appContext.registerShutdownHook() did not work for me. I had to use the provided solution, since it will register shutdown hook to the current JVM instance. – YetAnotherBot May 01 '18 at 13:22
7

Here is a subtle point you need to be aware of with "prototype" scoped beans.

For "prototype" scoped beans, Spring does not call the @PreDestroy method.

Here is the answer from the Spring official reference manual :

Section 1.5.2 (see here)

In contrast to the other scopes, Spring does not manage the complete lifecycle of a prototype bean**: the container instantiates, configures, and otherwise assembles a prototype object, and hands it to the client, with no further record of that prototype instance.

Thus, although initialization lifecycle callback methods are called on all objects regardless of scope, in the case of prototypes, configured destruction lifecycle callbacks are not called. The client code must clean up prototype-scoped objects and release expensive resources that the prototype bean(s) are holding.

To get the Spring container to release resources held by prototype-scoped beans, try using a custom bean post-processor, which holds a reference to beans that need to be cleaned up.

NOTE: This also applies to XML configuration.

jossefaz
  • 3,312
  • 4
  • 17
  • 40
Sampat Kumar
  • 492
  • 1
  • 6
  • 14
5

When you use @Scope("prototype") in your class @PreDestroy isn't working even though if you try to close with context.close(); or context.registerShutdownHook();

4

An old question, but wanted to share something I found.

I had a similar piece of code and initially thought that the @PreDestroy method was not being called. But then I added a print statement along with the LOGGER info() and was surprised to see that the print was executed. Apparently it is because the logback shuts itself down even before the @PreDestroy method is being called.

The following source can be useful: https://github.com/spring-projects/spring-framework/issues/24431.

Poulami Pal
  • 201
  • 3
  • 6
1

Old question but a solution would be to create a class to handle PreDestroy :

@Component
public class SetBeanProcessor implements BeanPostProcessor, BeanFactoryAware, DisposableBean {

    private BeanFactory beanFactory;
    private final List<Object> prototypeBeans = new LinkedList<>();

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (beanFactory.isPrototype(beanName)) {
            synchronized (prototypeBeans) {
                prototypeBeans.add(bean);
            }
        }
        return bean;
    }


    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }


    @Override
    public void destroy() throws Exception {

        // note : if we have several instance of a prototype (each instance is distinct) the method will be called several times ... 
        synchronized (prototypeBeans) {
            for (Object bean : prototypeBeans) {

                if (bean instanceof DisposableBean) {
                    DisposableBean disposable = (DisposableBean)bean;
                    try {
                        disposable.destroy();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            prototypeBeans.clear();
        }
    }

The class that use this class should be implemented this way :

@Component
public class myClass implements SomeClassName, DisposableBean {
    ...

    @Override
    public void destroy() throws Exception {
        System.out.println("# myClass: destroy() method called");       
    }
}
davidvera
  • 1,292
  • 2
  • 24
  • 55
0

I don't know why you want Spring to take care of this. Unless I misunderstood your question you can go with containers application life cycle.

Try writing a LifeCycle (jetty) and LifeCycleListener (tomcat) and override in LifeCyle onStart and onStop. And work a similar solution for LifeCycleListener in tomcat when the appropriate event takes place.

ssedano
  • 8,322
  • 9
  • 60
  • 98
  • 5
    If you do that, you induce a dependency between the shutdown code and the container. It's probably better to avoid it, and keep a single dependency to Spring, which is already there (or even better remove any dependency to Spring and use javax.inject annotations). – Laurent Grégoire Nov 04 '13 at 17:25
0

You can use method reference "close" for the execution of @PreDestroy methods in Spring:

Runtime.getRuntime().addShutdownHook(new Thread(applicationContext::close));
SM. Hosseini
  • 171
  • 1
  • 13