Ejemplo básico Quartz 2.2.1 + Tomcat 7.0.54 (con edición de expresión cron)

Quartz es una utilería que permite calendarizar tareas, o en otras palabras, ejecutar una tarea cada cierto tiempo. Funciona sobre Java SE o Java EE. El siguiente ejemplo requiere de las siguientes librerías:

  • quartz-2.2.1.jar
  • slf4j-api-1.7.7.jar
  • slf4j-simple-1.7.7.jar

No se requiere el archivo web.xml. Utiliza @WebListener y @WebServlet.

Estructura

El proyecto tiene la siguiente estructura (siguiendo la estructura de un proyecto típico de eclipse):

C:.
|
|
+---src
|   +---jobs
|   |       SimpleJob.java
|   |
|   +---listeners
|   |       QuartzListener.java
|   |
|   \---servlets
|           EditTriggerServlet.java
|
\---WebContent
    \---WEB-INF
        \---lib
                quartz-2.2.1.jar
                slf4j-api-1.7.7.jar
                slf4j-simple-1.7.7.jar

QuartzListener.java

El listener que aparece en este ejemplo es una subclase de listener de Quartz (org.quartz.ee.servlet.QuartzInitializerListener) para aprovechar algunas de sus operaciones (en este caso es la configuración inicial).

package listeners;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.annotation.WebListener;

import jobs.SimpleJob;

import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@WebListener
public class QuartzListener extends QuartzInitializerListener {

    private static final Logger LOG = LoggerFactory.getLogger(QuartzListener.class);

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        super.contextInitialized(sce);
        ServletContext ctx = sce.getServletContext();
        StdSchedulerFactory factory = (StdSchedulerFactory) ctx.getAttribute(QUARTZ_FACTORY_KEY);
        try {
            Scheduler scheduler = factory.getScheduler();
            JobDetail job = JobBuilder.newJob(SimpleJob.class).build();
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("simple").withSchedule(
                    CronScheduleBuilder.cronSchedule("0 0/1 * 1/1 * ? *")
            ).startNow().build();
            scheduler.scheduleJob(job, trigger);
            scheduler.start();
        } catch (Exception e) {
            LOG.error("Ocurri\u00f3 un error al calendarizar el trabajo", e);
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        super.contextDestroyed(sce);
    }

}

Cuando la aplicación se inicia en el Tomcat, se ejecuta este listener. Después de ejecutar el código de la clase padre, se crea un Job (una referencia a la clase que contiene el código de la tarea a realizar) y también crea un Trigger utilizando una expresión cron . La expresión que aparece en este ejemplo (0 0/1 * 1/1 * ? *) le indica al calendarizador que debe ejecutar el Job cada minuto.

NOTA: Para cualquier otra expresión, puedes generar una nueva utilizando http://www.cronmaker.com/

Durante la ejecución del código padre de este listener, un atributo es colocado en el contexto de la aplicación. Este atributo es la fábrica del calendarizador. Para mayor información, veáse http://quartz-scheduler.org/api/2.2.0/org/quartz/ee/servlet/QuartzInitia...


SimpleJob.java

La siguiente clase contiene el código de la tarea a realizar en el tiempo especificado (dependiendo del calendarizador).

package jobs;

import java.util.Locale;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class SimpleJob implements Job {

        public void execute(JobExecutionContext ctx) throws JobExecutionException {
                System.out.printf(new Locale("es", "MX"), "%tc Ejecutando tarea...%n", new java.util.Date());
        }
}

Únicamente se imprime un mensaje con la fecha actual.


EditTriggerServlet.java

El servlet muestra un formulario para actualizar la expresión cuando se realiza una petición GET. Y actualiza la expresión durante la petición POST desde el formulario.

package servlets;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Locale;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import listeners.QuartzListener;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;

@WebServlet("/edit")
public class EditTriggerServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();
        try {
            ServletContext ctx = req.getServletContext();
            StdSchedulerFactory factory = (StdSchedulerFactory) ctx.getAttribute(QuartzListener.QUARTZ_FACTORY_KEY);
            Scheduler scheduler = factory.getScheduler();
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(new TriggerKey("simple"));
            out.println("<form action=edit method=post>");
            out.printf("Expresi\u00f3n Cron: <input type=text name=exp value=\"%s\" required>",
                    trigger.getCronExpression());
            out.println("<button>Enviar</button>");
            out.println("</form>");
        } catch (Exception e) {
            out.print("<pre>");
            e.printStackTrace(out);
            out.print("</pre>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();
        try {
            String cronExpression = req.getParameter("exp");
            ServletContext ctx = req.getServletContext();
            StdSchedulerFactory factory = (StdSchedulerFactory) ctx.getAttribute(QuartzListener.QUARTZ_FACTORY_KEY);
            Scheduler scheduler = factory.getScheduler();
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("simple").withSchedule(
                    CronScheduleBuilder.cronSchedule(cronExpression)).startNow().build();
            Date date = scheduler.rescheduleJob(new TriggerKey("simple"), trigger);
            out.printf(new Locale("es", "MX"), "Trabajo re-calendarizado<br>Próxima ejecución:<br>%tc", date);
        } catch (Exception e) {
            out.print("<pre>");
            e.printStackTrace(out);
            out.print("</pre>");
        }
    }

}


En ejecución

Si hacemos una petición GET, el navegador nos mostrará un formulario donde podemos introducir otra expresión:

Por ejemplo, si queremos que ejecute la tarea cada 5 minutos... Primeramente generamos la expresión (usando http://www.cronmaker.com/)

E introducimos la expresión generada en el formulario:

La pantalla mostrará la fecha de la siguiente ejecución:


Consideraciones

  • El tiempo mínimo de calendarización utilizando una expresión cron es 1 minuto.
  • Por defecto, el número máximo de trabajos ejecutándose al mismo tiempo con este método son 10.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de sr.bug

Servicio

Se que se puede usar esta libreria para hacer un demonio en linux o servicio en windows, alguna idea de como es el proceso para crearlo e instalarlo?

Ejecuta un shell/bat

  • En Windows, ejecuta un bat al iniciar el sistema.
  • Y en Linux, ejecuta un shell al iniciar el sistema.

      Incluye lo que quieras en bat/shell. :)

Imagen de gorlok

+1 a Quartz!

Siempre uso Quartz en mis proyectos para manejar las tareas periódicas, de una forma muy similar a la explicada aquí

Y también usamos algún evento de inicialización del contexto web para configurar la tarea. Doy fe que cumple lo que promete, Quartz es gauchito :)

Es un buen ejemplo: simple y didáctico.