Quartz es un programador de tareas en Java. Para empezar, hay que decir que J2SE ya viene de serie con soporte para la programación de tareas, gracias a las clases Timer y TimerTask, que son capaces de lanzar un hilo por cada tarea que se ejecuta.
Sin embargo, Quartz va más allá, pues aporta un framework mucho más completo, y con características interesantes, como:
- Configuración declarativa (ficheros XML o properties), además de programática.
- Multitud de opciones de programación (por ejemplo, sintaxis tipo cron).
- Mantenimiento de estado de las tareas.
- Potente gestión de errores o tareas incompletas recuperables.
- Disponibilidad de Listeners no intrusivos sobre las tareas, los disparadores o sobre el propio programador.
- Integración con aplicaciones web vía Servlet o ContextListener.
- Integración con Spring.
- Soporte a entorno distribuido (cluster).
Es muy común la necesidad de dar de alta procesos periódicos asociados a una aplicación web. Quartz es especialmente útil precisamente porque elimina la obligación de tener crones de Linux o tareas programadas de Windows, de forma que toda nuestra aplicación, con la funcionalidad completa, reside en el WAR.
Estos son los pasos para dar de alta una tarea en una aplicación web de la forma más sencilla.
- Se descarga Quartz, y se introducen en el WEB-INF/lib de la aplicación el JAR del propio Quartz y todos de los que depende (están incluidos en la distribución).
- En el fichero web.xml se da de alta un listener, QuartzInitializerListener. Se trata de un ContextListener, que ejecuta automáticamente un método en el arranque de la aplicación (arranca el programador de tareas) y otro en la parada (obviamente, para el programador).
<web-app ... >
<display-name>QuartzExample</display-name>
<listener>
<listener-class>org.quartz.ee.servlet.QuartzInitializerListener</listener-class>
</listener>
</web-app>
- Una vez arrancado, el programador buscará su configuración. Por defecto, deberá estar en un fichero de nombre quartz.properties en el raíz del classpath (WEB-INF/classes). El contenido de este fichero para una tarea simple, sin gestión del estado, soporte para cluster, etc, sería:
# Programador #
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
# Hilos #
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
# Almacenamiento #
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
# Plugins #
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.validating=false
- Las últimas líneas del quartz.properties están configurando un plugin capaz de incorporar las tareas y disparadores de un fichero XML. De nuevo, usaremos convención sobre configuración, de modo que llamaremos a ese fichero quartz_jobs.xml, y también lo situaremos en WEB-INF/classes, con el siguiente contenido:
<quartz ... >
<job>
<job-detail>
<name>QuartzExampleJob</name>
<group>ExampleGroup</group>
<description>ExampleJob</description>
<job-class>com.javisjava.quartz.jobs.QuartzExampleJob</job-class>
<job-data-map allows-transient-data="false">
<entry>
<key>nombre</key>
<value>Javier</value>
</entry>
</job-data-map>
</job-detail>
<trigger>
<cron>
<name>QuartzExampleTrigger</name>
<group>ExampleTriggerGroup</group>
<job-name>QuartzExampleJob</job-name>
<job-group>ExampleGroup</job-group>
<!-- Se dispara cada 10 segundos -->
<cron-expression>0/10 * * * * ?</cron-expression>
</cron>
</trigger>
</job>
</quartz>
Este XML indica que debe ejecutarse la tarea definida en la clase QuartzExampleJob, con un parámetro de configuración de clave nombre y valor Javier. La tarea se ejecutará según el calendario definido en el disparador de tipo cron QuartzExampleTrigger (cada 10 segundos).
- Las clases que contienen las tareas deben implementar la interfaz org.quartz.Job, que sólo contiene el método execute. Éste es nuestro ejemplo, que lanza un mensaje en la consola, apoyado en el parámetro que se le pasa en el XML (pueden ser múltiples parámetros, mientras su clave sea distinta).
package com.javisjava.quartz.jobs;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class QuartzExampleJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Hola "
+ context.getJobDetail().getJobDataMap().getString("nombre"));
}
}
- Al arrancar la aplicación web, aparecerá “Hola Javier” en la consola del contenedor cada 10 segundos.
Os dejo un WAR totalmente funcional que incluye el código para poder modificar la tarea fácilmente.
Como os he dicho, Quartz se puede integrar también en la configuración de Spring, que es el modo recomendado si estáis usando este framework. Hay una sección de la referencia de Spring que cubre cómo hacerlo.
Para profundizar en el manejo de Quartz, acudid a la documentación, o al libro monográfico sobre este framework que ha escrito Chuck Cavaness.
*Edición: tras el comentario de Paul donde pregunta cómo actualizar el trigger una vez iniciado el proceso, he subido proyecto Eclipse muy sencillo donde se muestra la forma de hacerlo.
Me ha gustado tu articulo de Quartz, lo he utilizado integrado en Spring y no me ha dado problemas, aunque reconozco que he huido de los jobs con persistencia….lagarto, lagarto. He buceado un poco por todo el blog y me ha proporcionado un buen rato de lectura y algun que otro descubrimiento
.
gracias.
Quartz inyectado con Spring es mucho más elegante. Desgraciadamente en España aún hay muchos proyectos que se hacen con arquitecturas del pasado. Si las consultoras más grandes están usando JSP spaghetti en proyectos estratégicos, la programación orientada al aspecto y la inyección de dependencias no saben ni lo que son.
Los jobs con persistencia tienen su uso, cuando no hay otra opción. Por ejemplo, en un entorno de alta disponibilidad con balanceo de carga, donde una de las máquinas puede haberse caído, y debe ser otra la que realice la tarea (contando con que no se desee que la ejecuten todas las máquinas simultáneamente, porque entonces tampoco haría falta persistencia). También sirve en tareas donde debes mantener el estado de la ejecución por alguna razón. Pero, efectivamente, estos casos son minoría, frente a las típicas cachés o procesos batch contra base de datos.
Que tal! Javi!. Muy bueno tu blog!. estoy viendo el ejemplo que pusiste sobre quartz. Tengo el siguiente problema cuando subo el .WAR que dejas disponible sobre un tomcat 5.5:
– Puedo realizar el upload sobre Tomcat. Pero no puedo arrancar la aplicacion. Simplemente cuando le doy click a arrancar no pasa nada.
- En catalina.out tengo esto:
#################################################################
[...]
#################################################################
Espero que puedas ayudarme!!!! Desde ya, muchisimas gracias!!!!!!!!!!!!!!!
Slds!
Javier! Como estas!. te pido disculpas por el mensaje anterior. Pero solucione el problema!. Mi tomcat estaba cargando en java 5! por que lo tenia cargado como java_home en el catalina.sh!!! Deje que la version de java sea la 6 y funciona perfecto!!!
Espectacular el blog!
Un abrazo!
Hola Javi, es la primera vez que entro en tu blog.
Estoy realizando la documentación de mi PFC, donde he incluido Quartz con Spring, y la verdad es que este framework es sorprendente.
Un 10 por tu explicación!
Un saludo
Hola Javi, verdaderamente interesante tu blog, hasta ahora leo sobre Quartz, creo q es la solucion para lo q necesito… q es::: q se ejecute un web service todos los dias a una determinada hora… ¿sera q el Quartz se puede programar para q se ejecute a una determinada hora especifica diario, independientemente de la hora q se suba el server? es decir q no haya necesidad de decirle ejecutese tanto tiempo despues de q se suba el sistema sino ejecutese diario a las 2:30 am…
agradezco en lo q me puedas colaborar
La sintaxis es la estándar de los Cron, por lo que puedes hacer eso y mucho más. La expresión que tú necesitas en la configuración es ésta:
<cron-expression>0 30 2 * * ?</cron-expression>Hola Javier, tengo una duda. Me bajé tu WAR sobre Quartz y lo estoy ejecutando sobre un JBoss 4.0.5GA. Al arrancarse el JBoss, me da un fallo, algo de “Appender” relacionado con el log4j.xml y un ClassCastException. El caso es que luego la aplicación funciona, es decir, aparece tu nombre en pantalla cada 10 segundos, pero todos esos fallos hacen que no pueda usar Quartz como me gustaría en mi proyecto.
¿Sabes qué puede estar pasando?
Muchísimas gracias de antemano.
log4j:ERROR “org.jboss.logging.util.OnlyOnceErrorHandler” was loaded by [org.jboss.system.server.NoAnnotationURLClassLoader@6e1408].
log4j:ERROR Could not create an Appender. Reported error follows.
java.lang.ClassCastException
Hola Sandro. El problema que tienes no tiene que ver con Quartz sino con Log4J y el hecho de que JBoss tiene este motor de logging configurado internamente para su propio uso, de una forma que genera conflictos con las aplicaciones. No soy experto en JBoss, porque no me va el modelo EJB de desarrollo, pero creo que en este enlace podrás encontrarás la solución (apartado 10.3.7.).
http://docs.jboss.org/process-guide/en/html/logging.html
Si no te quieres complicar mucho, siempre puedes probarlo en un Tomcat.
Igualmente, es normal que la aplicación funcione, lo único que no se ha arrancado correctamente es el logging, tal y como lo tengo configurado.
Muchas gracias Javier.
Voy a echarle un vistazo ahora mismo.
Buenas Javi,
Comentar que Quartz está bastante bien, lo he usado recientemente en un proyecto de alta disponibilidad (en cluster, con persistencia de los jobs xD).
Lo que dices de las consultoras grandes es cierto, pero a veces no y el mío es un caso. Hemos podido decidir utilizar Quartz, o Logback o generación de logs dinámicos con aspectos (AspectJ), generación de estadísticas con programación orientada a eventos (ESPER) así que depende mucho de la consultora, la pasta del cliente y sobretodo de la iniciativa de los desarrolladores/analistas o del responsable de turno.
Saludos
Muy buen Blog!!!!!
).
Me fue de ayuda, porque estaba buscando algo de esto….
Articulos de este estilo son muy buenos para quien está aprendiendo (mi caso
Muchas gracias.
Buenos días he leido vuestros comentario y veo que manejais bastante el Quartz, yo lo he utilizado alguna vez pero no controlo mucho, esta vez me he encontrado, con 4 servidores en modo cluster. Y la tarea que programo se lanza en los 4 servidores a la vez.
Solo quiero que se ejecute en un sitio, da igual en cual. Sabéis como puedo hacerlo, hay alguna configuración??
Felicidades por el blog
Tienes que configurar Quartz también en modo cluster.
Básicamente se trata de usar la base de datos como almacén de las tareas que se llevan a cabo.
Cada instancia intentará realizar la tarea lo antes posible, pero sólo si al consultar a la base de datos (transaccionalmente), ve que es la primera.
Hay ciertas cosas que hay que tner en cuenta, como que los servidores deben estar bien sincronizados.
La información sobre cómo configurar el cluster la tienes aquí:
http://www.opensymphony.com/quartz/wikidocs/ConfigJDBCJobStoreClustering.html
Y el detalle de funcionamiento del almacén de tareas en base de datos, aquí:
http://www.opensymphony.com/quartz/wikidocs/ConfigJobStoreTX.html
http://www.opensymphony.com/quartz/wikidocs/ConfigJobStoreCMT.html (si el servidor soporta JTA, es decir, en general, para contenedores EJB)
Si te bajas la distribución completa de Quartz, el ejemplo 13 tiene la configuración necesaria, y en docs/dbTables dispones de los scripts para la creación de las tablas necesarias en la mayoría de bases de datos del mercado.
Eso sí, yo cambiaría la configuración del ejemplo, poniendo el instanceId del properties de Quartz a AUTO, como sugiere la documentación. De esa forma, no tienes que tener un fichero properties diferente en cada instancia, hecho que es muy cómodo si utilizas un almacenamiento compartido para guardar la aplicación o algún proceso de sincronización de ficheros.
Hola Javier antes que nada felicitaciones por el blog, queria hacerte una consulta, hace tiempo que estoy usando quartz pero tengo un problema (uso como servidor JBoss), en mi aplicacion existe una tarea que se ejecuta cada 3 minutos, ahora la tarea corre bien, pero cada X dias esta deja de correr, no encuentro el problema ya que al reiniciar el servidor funciona normalmente de nuevo, si pudieras ayudarme te agradeceria mucho.
Saludos
Que tal Javi.
El blog me parece muy bueno.
Pero tengo un problema y no sé cómo solucionarlo con el quartz y es el siguiente.
Necesito que se ejecute una tarea todos los días lunes a las 7 de la mañana, al parecer el quartz lo puede hacer, pero no sé como configurarlo.
Espero me puedan ayudar.
Saludos.
La expresión cron que tendrías que introducir en la etiqueta
cron-expressionsería:0 0 7 ? * MONSignifica “en el segundo 0, minuto 0, hora 7, de ninguna fecha específica del mes, todos los meses, los lunes”.
Tienes más documentación sobre la sintaxis cron en http://en.wikipedia.org/wiki/CRON_expression y http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html.
Hola Javier,
Excelente blog, funciona de maravilla. Sólo una duda Javi, espero me puedas ayudar, la configuración en el quartz_jobs.xml me parece ideal pero ¿cómo podría actualizar el ‘cron-expression’ desde código una vez ejecutado el Job?
He intentado realizarlo cambiando los valores de la referencia al Trigger que se obtiene a través del parametro ‘context’ del método execute(JobExecutionContext context) de esta forma context.getTrigger(), en la clase que implementamos el inteface Job, sin embargo no encuentro la manera de reestablecer una nueva hora de ejecución.
De antemano muchas gracias.
Saludos cordiales!!
Se puede acceder al scheduler, a los triggers y a los jobs programáticamente, por su nombre y grupo. Por ejemplo, dentro del método execute del propio job podrías incluir este código, que se ejecutase bajo ciertas condiciones:
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
CronTrigger trigger = new CronTrigger("trigger1", "group1", "0/5 * * * * ?");
trigger.setJobName("job1");
trigger.setJobGroup("group1");
scheduler.rescheduleJob("trigger1", "group1", trigger);
Eso actualizaría el trigger. Hay que poner atención en los nombres y grupos del job y del trigger que quieres modificar.
Dejo un proyecto Eclipse con un ejemplo sencillo, donde después de ejecutarse 5 veces el job cada 2 segundos, el trigger se actualiza de modo que pasa a ejecutarse cada 5 segundos.
Estimado Javier,
Muy bien, muchas gracias! Esto me suena a que se podrían combinar. Voy a intentarlo.
Saludos cordiales!
Felicidades muy buen BLOG, sobre el tema aqui mi aporte de este muy buen Framework.
http://frameworksjava2008.blogspot.com/search/label/Quarz
Saludos.