web-dev-qa-db-fra.com

Enregistrer shutDownHook dans une application Web

Comment pouvons-nous enregistrer un crochet d'arrêt dans une application Web?

Existe-t-il des options pour l'enregistrer dans web.xml ou dans applicationContext.xml?

Je sais que si nous utilisons une application avec la classe principale, c'est simple.

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    context.registerShutdownHook();

Mais qu'en est-il des applications Web? Comme il utilise ContextListener

21
slisnychyi

registerShutdownHook () dans une application autonome (non Web):

L'annotation @PreDestroy est utilisée sur la méthode de bean pour être avertie lorsque le bean est supprimé du contexte ou lorsque le contexte est en cours de fermeture.

L'événement d'arrêt est déclenché lorsque context.close() ou context.registerShutdownHook() est appelé.

@Component(value="someBean")
public class SomeBean {

    @PreDestroy
    public void destroy() {
        System.out.println("Im inside destroy...");
    }
}

J'espère que tu le sais déjà.


registerShutdownHook () dans l'application Web:

Dans une application Web, DispatcherServlet/ContextListener crée le contexte d'application et le contexte sera fermé lors de l'arrêt du serveur. Vous n'avez pas besoin d'appeler explicitement context.close() ou context.registerShutdownHook().

Lorsque le serveur sera arrêté, les méthodes @PreDestory sur votre bean seront notifiées automatiquement.

19
Kalyan

Dans les applications Web, vous pouvez utiliser une ServletContextListener qui se déclenche lorsque votre application est déployée et non déployée:

public class MyServletContextListener implements ServletContextListener {
    public void contextInitialized(ServletContextEvent sce) {
        //application is being deployed
    }
    public void contextDestroyed(ServletContextEvent sce) {
        //application is being undeployed
    }
}

Vous pouvez accéder à vos beans Spring en récupérant le contexte Spring actuel:

public void contextDestroyed(ServletContextEvent sce) {
    ServletContext ctx = sce.getServletContext();
    WebApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext(ctx);
    //retrieve your Spring beans here...
    SomeSpringBean bean = (SomeSpringBean)ctx.getBean("someSprinbgBean");
    //...
}
4
Luiggi Mendoza

En utilisant Spring 3+, vous pouvez ajouter un ContextCleanupListener au contexte de l’application.

Enregistrez votre écouteur au démarrage de la manière suivante (vous préférerez peut-être utiliser la configuration xml mais la même chose s'applique)

package com.myapp

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextCleanupListener;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext)
            throws ServletException {
        WebApplicationContext appContext = getContext();


        servletContext.addListener(new ContextLoaderListener(appContext));

        // line adding an implementation of ContextCleanupListener
        servletContext.addListener(new MyWebApplicationCleanupListener());

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(appContext));
            dispatcher.setLoadOnStartup(1);
            dispatcher.addMapping("/");
    }

    private AnnotationConfigWebApplicationContext getContext() { 
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.setConfigLocation("com.myapp");
        return context;
    }
} 

Implémentation de ContextCleanupListener qui exécute votre code d'arrêt:

package com.myapp;

import javax.servlet.ServletContextEvent;
import com.myapp.resources.requiring.clean.shutdown
import org.springframework.web.context.ContextCleanupListener;

public class MyWebApplicationCleanupListener extends ContextCleanupListener {

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        // put your shutdown code in here

        MyResourceNeedingShutdown dataStore = MyResourceNeedingShutdown.getInstance();
        dataStore.shutdown();
    }

}

Par exemple, lorsque vous exécutez Tomcat et que vous appuyez sur CTRL + C pour le fermer, la méthode contextDestroyed apparaît immédiatement dans le débogueur si vous y placez un point d'arrêt. 

2
John Deverall

@Luiggi Mendoza answer a commencé à fonctionner lorsque j’ajoute une entrée dans web.xml.

<web-app ...>
   <listener>
    <listener-class>
             com....MyServletContextListener
        </listener-class>
   </listener>
</web-app>

vous pouvez voir la trace de la pile d'initialisation/notification de l'objet écouteur par Tomcat; ce qui est beaucoup avant le printemps fait.

com....MyServletContextListener.init(nothing but calling @PostConstruct)
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:62)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
    at Java.lang.reflect.Method.invoke(Method.Java:498)
    at org.Apache.catalina.core.DefaultInstanceManager.postConstruct(DefaultInstanceManager.Java:203)
    at org.Apache.catalina.core.DefaultInstanceManager.postConstruct(DefaultInstanceManager.Java:188)
    at org.Apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.Java:143)
    at org.Apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.Java:119)
    at org.Apache.catalina.core.StandardContext.listenerStart(StandardContext.Java:4649)
    at org.Apache.catalina.core.StandardContext.startInternal(StandardContext.Java:5189)
    at org.Apache.catalina.util.LifecycleBase.start(LifecycleBase.Java:150)
    at org.Apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.Java:724)
    at org.Apache.catalina.core.ContainerBase.addChild(ContainerBase.Java:700)
    at org.Apache.catalina.core.StandardHost.addChild(StandardHost.Java:734)
    at org.Apache.catalina.startup.HostConfig.deployWAR(HostConfig.Java:952)
    at org.Apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.Java:1823)
    at Java.util.concurrent.Executors$RunnableAdapter.call(Executors.Java:511)
    at Java.util.concurrent.FutureTask.run(FutureTask.Java:266)
    at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1142)
    at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:617)
    at Java.lang.Thread.run(Thread.Java:745)

Mais la chose importante est que @PreDestroy est appelé en premier par Spring, puis après que contextDestroyed est appelé et encore une fois @PreDestroy est appelé par un thread non-spring.

Donc, si vous voulez compléter certains travaux; à ce moment-là, si vous voulez vous assurer que d'autres threads de ressources sont disponibles, maintenez ce @PreDestroy.

@PreDestroy
public void cleanup() {
    eventTaskExecutor.shutdown();
    try {
        /**
         * This is blocking call to avoid other threads (like logger demon thread)
         * not closed before this completes the job. Else worker thread cannot log
         * event.
         * 
         * This will be the case when thread is busy in getting the web response,
         * better will wait for that, and log the web response.
         * 
         */
        eventTaskExecutor.awaitTermination(20, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }   
}

Sachez simplement que la procédure ci-dessous est un moyen supplémentaire d’obtenir le hook @postConstruct dans la classe.

@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
    applicationContext.getBean("myRelatedClass", MyRelatedClass.class);
}

Une autre chose est que @PreDestroy est appelé uniquement pour les objets singleton non disponibles pour les objets prototypes.

0