¿Cómo ejecutar una tarea en segundo plano en una aplicación web basada en servlet?

97

Estoy usando Java y quiero mantener un servlet ejecutándose continuamente en mi aplicación, pero no entiendo cómo hacerlo. Mi servlet tiene un método que da recuentos diarios de usuarios de una base de datos, así como el recuento total de usuarios de toda la base de datos. Así que quiero mantener el servlet funcionando continuamente para eso.

pritsag
fuente
¿Qué quieres decir con "correr continuamente"?
skaffman
1
¿Qué quieres decir con correr continuamente? Se ejecutará mientras se ejecute su servidor de aplicaciones
fmucar
2
No entiendo por qué tiene que ejecutarse continuamente ... si alguien quiere el 'recuento de usuarios', ¿llama a su método de servlet y se lo da?
trojanfoe
@trojanfoe En realidad, quiero el recuento de usuarios a diario, así que para eso tendré que ejecutar el servlet manualmente todos los días, así que en lugar de hacerlo, quiero ejecutar el servlet de forma continental, por lo que no necesitaré ejecutar el servlet todos los días.
pritsag
1
@pritsag: Un servlet está ahí para atender las solicitudes de los usuarios, no para ejecutar trabajos por lotes.
skaffman

Respuestas:

217

Su problema es que no entiende el propósito del servlet . Tiene la intención de actuar sobre solicitudes HTTP, nada más. Solo desea una tarea en segundo plano que se ejecute una vez al día.

¿EJB disponible? Utilizar@Schedule

Si su entorno admite EJB (es decir, un servidor Java EE real como WildFly, JBoss, TomEE, Payara, GlassFish, etc.), utilice @Scheduleen su lugar. Aquí hay unos ejemplos:

@Singleton
public class BackgroundJobManager {

    @Schedule(hour="0", minute="0", second="0", persistent=false)
    public void someDailyJob() {
        // Do your job here which should run every start of day.
    }

    @Schedule(hour="*/1", minute="0", second="0", persistent=false)
    public void someHourlyJob() {
        // Do your job here which should run every hour of day.
    }

    @Schedule(hour="*", minute="*/15", second="0", persistent=false)
    public void someQuarterlyJob() {
        // Do your job here which should run every 15 minute of hour.
    }

    @Schedule(hour="*", minute="*", second="*/5", persistent=false)
    public void someFiveSecondelyJob() {
        // Do your job here which should run every 5 seconds.
    }

} 

Sí, eso es todo. El contenedor lo recogerá y gestionará automáticamente.

¿EJB no está disponible? UtilizarScheduledExecutorService

Si su entorno no es compatible con EJB (es decir, no está utilizando un servidor Java EE real, sino un contenedor de servlets básico como Tomcat, Jetty, etc.), utilice ScheduledExecutorService. Esto puede ser iniciado por a ServletContextListener. Aquí hay un ejemplo de inicio:

@WebListener
public class BackgroundJobManager implements ServletContextListener {

    private ScheduledExecutorService scheduler;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new SomeDailyJob(), 0, 1, TimeUnit.DAYS);
        scheduler.scheduleAtFixedRate(new SomeHourlyJob(), 0, 1, TimeUnit.HOURS);
        scheduler.scheduleAtFixedRate(new SomeQuarterlyJob(), 0, 15, TimeUnit.MINUTES);
        scheduler.scheduleAtFixedRate(new SomeFiveSecondelyJob(), 0, 5, TimeUnit.SECONDS);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        scheduler.shutdownNow();
    }

}

Donde las clases de trabajo se ven así:

public class SomeDailyJob implements Runnable {

    @Override
    public void run() {
        // Do your daily job here.
    }

}
public class SomeHourlyJob implements Runnable {

    @Override
    public void run() {
        // Do your hourly job here.
    }

}
public class SomeQuarterlyJob implements Runnable {

    @Override
    public void run() {
        // Do your quarterly job here.
    }

}
public class SomeFiveSecondelyJob implements Runnable {

    @Override
    public void run() {
        // Do your quarterly job here.
    }

}

Nunca piense en usar java.util.Timer/ java.lang.Threaden un entorno basado en Java EE / Servlet

Por último, pero no menos importante, nunca use directamente java.util.Timery / o java.lang.Threaden Java EE. Esta es la receta para los problemas. Se puede encontrar una explicación detallada en esta respuesta relacionada con JSF sobre la misma pregunta: Generación de subprocesos en un bean administrado por JSF para tareas programadas usando un temporizador .

BalusC
fuente
9
@BalucS Gracias, señor, su solución me ayudó y aprendí sobre ScheduledExecutorService, que era nuevo para mí como nuevo en Java. Gracias una vez más.
pritsag
@BalusC: ¿Dónde debería colocarse la clase UpdateCounts en web.xml?
Ashwin
1
@Ashwin web.xml es un descriptor de implementación . La clase UpdateCount no está relacionada con la implementación, por lo que no tiene que colocarse en web.xml
informatik01
12
Un tema crucial con un ScheduledExecutorService: asegúrese de capturar todas las excepciones en su ejecutor. Si una excepción se escapa de su runmétodo, el ejecutor deja de ejecutar silenciosamente. Esto es una característica, no un error. Lea el documento y estudie buscando en Google.
Basil Bourque
1
@Agi: eso sucederá si scheduler.shutdownNow()no se invoca correctamente como en el ejemplo. Si no se invoca, el hilo de programación seguirá ejecutándose.
BalusC
4

Sugeriría usar una biblioteca como Quartz para ejecutar la tarea a intervalos regulares. ¿Qué hace realmente el servlet? ¿Te envía un informe?

Tornado
fuente
sí, me da el recuento del usuario creado por día y también el recuento del total de usuarios en mi base de datos.
pritsag
1
huuu? ¿Puede describir la arquitectura COMPLETA de su sistema? Estoy perdido.
Twister
@Twister soy nuevo en Java y en la fase de aprendizaje, señor, y realmente no sé mucho sobre los servlets.
pritsag
El problema no se trata de servlet. ¿Cuál es la aplicación de la que estás hablando? (PD: es una mala idea eliminar sus comentarios, especialmente los comentarios a los que respondí)
Twister
@twister cuando el usuario acceda a la aplicación, obtendrá todos los detalles como cuántos usuarios se crean hoy, cuántos usuarios se crearon hasta ahora, etc., y quiero ejecutar el servlet en segundo plano de forma continental para que el usuario pueda obtener las actualizaciones . Sé que esta no es la explicación adecuada. (PD: sé que fue una mala idea, lo siento)
pritsag
3

Implementar dos clases y llamar startTask()en main.

public void startTask()
{
    // Create a Runnable
    Runnable task = new Runnable() {
        public void run() {
            while (true) {
                runTask();
            }
        }
    };

    // Run the task in a background thread
    Thread backgroundThread = new Thread(task);
    // Terminate the running thread if the application exits
    backgroundThread.setDaemon(true);
    // Start the thread
    backgroundThread.start();
}

public void runTask()
{
    try {
        // do something...         
        Thread.sleep(1000);

    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
Ali Alimohammadi
fuente
3
Definitivamente, esta NO es la forma de hacerlo en una aplicación web; mire la respuesta anterior de @BalusC en su lugar, él tiene razón aquí y yo diría que puede confiar en todas sus respuestas.
Yoshiya
0

En un sistema de producción que puede tener varios contenedores que no son jee en funcionamiento. Use un programador no empresarial como el programador Quartz, que se puede configurar para usar una base de datos para la tarea maamgememt.

Jeryl Cook
fuente