"Quartz.NET ofrece una programación de tareas flexible a sus aplicaciones .NET, lo que le permite crear programaciones complejas con facilidad utilizando expresiones Cron."
--Marko Lahma, Lead Developer of Quartz.NET
Quartz.NET es una biblioteca de programación potente y flexible que facilita la definición y ejecución de procesos en intervalos específicos. Cuando se integra con Spring.NET, Quartz.NET permite la configuración declarativa y la inyección de dependencias, lo que mejora la productividad y reduce la necesidad de administrar temporizadores manualmente.
¿Por qué utilizar Quartz.NET con Spring.NET?
Quartz.NET simplifica la programación de tareas al permitirle configurar expresiones o especificar propiedades que determinan la frecuencia con la que se debe ejecutar un proceso. Al aprovechar Spring.NET para la inyección de dependencias, puede administrar el ciclo de vida de sus objetos de trabajo y ejecutar tareas programadas dentro de un contexto bien definido. Esta combinación ofrece una solución sólida para la ejecución periódica de tareas sin la complejidad de la gestión manual de temporizadores.
Ejemplo de aplicación de consola
Antes de profundizar en el ejemplo, es esencial abordar los posibles conflictos de versiones de ensamblador que puedan surgir. Asegúrese de utilizar versiones compatibles de los ensamblajes necesarios:
- Assemblies Spring.NET: Spring.Aop.dll, Spring.Core.dll, Spring.Data.dll, Spring.Scheduling.Quartz.dll
- Logging Library: Common.Logging.dll (incluido en la distribución Spring.NET)
- Assembly de Quartz.NET: Quartz.dll
Versiones de Assemblies utilizadas
- Spring.NET: Versión 1.3.1.40711
- Common.Logging: Versión 1.2.0.0
- Quartz.NET: Versión 1.0.3.3
Código de aplicación de consola
El siguiente código demuestra cómo usar la clase XmlApplicationContext de Spring.NET para cargar la configuración desde spring-objects.xml y ejecutar los trabajos programados definidos allí:
using System;
using Spring.Context.Support;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press Enter to initialize the service");
Console.ReadKey();
try
{
XmlApplicationContext ctx = new XmlApplicationContext("spring-objects.xml");
Console.WriteLine("Spring configuration successful, Quartz jobs running");
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine("---Press Enter to exit---");
Console.ReadLine();
}
Console.WriteLine("---Press Enter to exit---");
Console.ReadLine();
}
}
Configuración de Spring.NET (spring-objects.xml)
El archivo de configuración spring-objects.xml define los trabajos de Quartz y sus disparadores:
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:aop="http://www.springframework.net/aop"
xmlns:db="http://www.springframework.net/database"
xmlns:tx="http://www.springframework.net/tx">
<!-- Scheduling Task -->
<object name="MyJob" type="Spring.Scheduling.Quartz.JobDetailObject, Spring.Scheduling.Quartz">
<property name="JobType" value="PruebaCronTrigger.MyJob, PruebaCronTrigger"/>
<property name="JobDataAsMap">
<dictionary>
<entry key="UserName" value="Ingecaam"></entry>
</dictionary>
</property>
</object>
<object id="JobService" type="PruebaCronTrigger.JobService, PruebaCronTrigger">
<property name="UserName" value="Ingecaam"/>
</object>
<object id="jobDetail" type="Spring.Scheduling.Quartz.MethodInvokingJobDetailFactoryObject, Spring.Scheduling.Quartz">
<property name="TargetObject" ref="JobService" />
<property name="TargetMethod" value="DoJobWork" />
<property name="Concurrent" value="false" />
</object>
<object id="JobSimpleTrigger" type="Spring.Scheduling.Quartz.SimpleTriggerObject, Spring.Scheduling.Quartz">
<property name="jobDetail" ref="jobDetail" />
<property name="startDelay" value="5s" />
<property name="repeatInterval" value="5s" />
</object>
<object id="JobCronTrigger" type="Spring.Scheduling.Quartz.CronTriggerObject, Spring.Scheduling.Quartz">
<property name="jobDetail" ref="MyJob" />
<property name="cronExpressionString" value="0/20 * * * * ?" />
</object>
<object type="Spring.Scheduling.Quartz.SchedulerFactoryObject, Spring.Scheduling.Quartz">
<property name="triggers">
<list>
<ref object="JobCronTrigger" />
<!--<ref object="JobSimpleTrigger" />-->
</list>
</property>
</object>
</objects>
Explicación de la configuración
Configuración de tareas: Lo que hace Quartz es ejecutar las tareas que se definan en el objeto SchedulerFactoryObject.
<object type="Spring.Scheduling.Quartz.SchedulerFactoryObject, Spring.Scheduling.Quartz">
<property name="triggers">
<list>
<ref object="JobCronTrigger" />
<!--<ref object="JobSimpleTrigger" />-->
</list>
</property>
</object>
Tipos de disparadores: Quartz admite dos tipos de disparadores:
SimpleTriggerObject: Configurado con propiedades como startDelay y repeatInterval para definir la periodicidad.
<object id="JobSimpleTrigger" type="Spring.Scheduling.Quartz.SimpleTriggerObject, Spring.Scheduling.Quartz">
<property name="jobDetail" ref="jobDetail" />
<property name="startDelay" value="5s" />
<property name="repeatInterval" value="5s" />
</object>
Al utilizar SimpleTriggerObject podemos de una manera sencilla configurar por medio de propiedades (startDelay y repeatInterval) la periodicidad con la que queremos que se ejecute la tarea, en el caso del ejemplo cada 5 segundos.
CronTriggerObject: Utiliza expresiones Cron para definir patrones de programación complejos.
<object id="JobCronTrigger" type="Spring.Scheduling.Quartz.CronTriggerObject, Spring.Scheduling.Quartz">
<property name="jobDetail" ref="MyJob" />
<property name="cronExpressionString" value="0/20 * * * * ?" />
</object>
CronTriggerObject es más robusto y nos permite por medio de expresiones, definir una periodicidad basada en calendario, se pueden especificar periodicidades como: todos los viernes al mediodía", o "todos los días laborables y de 9:30 am", o incluso "cada 5 minutos 09 a.m.-10 a.m. todos los lunes, miércoles y viernes durante enero".
Ejemplo: El SimpleTriggerObject en su propiedad ref indica la configuración jobDetail y dicha configuración presenta esta información:
<object id="jobDetail" type="Spring.Scheduling.Quartz.MethodInvokingJobDetailFactoryObject, Spring.Scheduling.Quartz">
<property name="TargetObject" ref="JobService" />
<property name="TargetMethod" value="DoJobWork" />
<property name="Concurrent" value="false" />
</object>
Como se puede ver, en TargetObject se hace referencia al objeto y en TargetMethod al método que se ejecuta periodicamente.
Spring también nos permite configurar en forma declarativa dicho objeto, permitiendo que podamos asignar valor a sus propiedades:
<object id="JobService" type="PruebaCronTrigger.JobService, PruebaCronTrigger">
<property name="UserName" value="Ingecaam"/>
</object>
JobService Class
namespace PruebaCronTrigger
{
public class JobService
{
private string _username;
public string UserName
{
get { return _username; }
set { _username = value; }
}
public void DoJobWork()
{
Console.WriteLine("{0} DoJobWork called, user name: {1}", DateTime.Now, _username);
}
}
}
Vemos como posee la propiedad UserName, el método DoJobWork y el nombre de su namespace es PruebaCronTrigger.
Cuando en la lista de las tareas, solo se configura el SimpleTriggerObject:
<object type="Spring.Scheduling.Quartz.SchedulerFactoryObject, Spring.Scheduling.Quartz">
<property name="triggers">
<list>
<ref object="JobSimpleTrigger" />
<!--<ref object="JobCronTrigger" />-->
</list>
</property>
</object>
La salida del programa es:
Es decir, la tarea se ejecuta cada 5 segundos que fue lo configurado en el SimpleTriggerObject.
Para el caso del CronTriggerObject el objeto que se referencia es MyJob:
<object name="MyJob" type="Spring.Scheduling.Quartz.JobDetailObject, Spring.Scheduling.Quartz">
<property name="JobType" value="PruebaCronTrigger.MyJob, PruebaCronTrigger"/>
<property name="JobDataAsMap">
<dictionary>
<entry key="UserName" value="Ingecaam"></entry>
</dictionary>
</property>
</object>
MyJob Class
namespace PruebaCronTrigger
{
public class MyJob : Quartz.JobSupport.Job
{
private string _userName;
public string UserName
{
get { return _userName; }
set { _userName = value; }
}
protected override void ExecuteInternal(Quartz.JobExecutionContext context)
{
Console.WriteLine("{0}: ExecuteInternal called, user name: {1}, next fire time {2}",
DateTime.Now, _userName, context.NextFireTimeUtc.Value.ToLocalTime());
}
}
}
En este caso, lo que se ejecuta periodicamente es lo definido en la sobreescritura del método ExecuteInternal. La periodicidad se define por medio de una expresión en la propiedad cronExpressionString del objeto CronTriguerObject.
<object id="JobCronTrigger" type="Spring.Scheduling.Quartz.CronTriggerObject, Spring.Scheduling.Quartz">
<property name="jobDetail" ref="MyJob" />
<property name="cronExpressionString" value="0/20 * * * * ?" />
</object>
La expresión Cron configurada de periodicidad es esta:
0/20 * * * * ?
Lo que significa cada 20 segundos, por tanto, si en la lista de tareas solo se configura la tarea asociada al CronTriggerObject:
<object type="Spring.Scheduling.Quartz.SchedulerFactoryObject, Spring.Scheduling.Quartz">
<property name="triggers">
<list>
<ref object="JobCronTrigger" />
<!--<ref object="JobSimpleTrigger" />-->
</list>
</property>
</object>
La salida del programa es:
Combinando múltiples disparadores
También se puede configurar varias tareas para que se ejecuten en diferentes intervalos combinando SimpleTriggerObject y CronTriggerObject:
Por ejemplo, al configurar ambas tareas, la del SimpleTriggerObject y la del CronTriggerObject:
<object type="Spring.Scheduling.Quartz.SchedulerFactoryObject, Spring.Scheduling.Quartz">
<property name="triggers">
<list>
<ref object="JobCronTrigger" />
<ref object="JobSimpleTrigger" />
</list>
</property>
</object>
La salida del programa es esta:
Nota: para que el ejemplo funcione, el archivo spring-objects.xml debe tener definido el BuildAction a Content.
A la hora de necesitar configurar una periodicidad se debe conocer muy bien como construir expresiones Cron como la usada en el ejemplo. A continuación, una pequeña guía.
Expresiones Cron
Las expresiones Cron son una herramienta poderosa para programar tareas con precisión en sistemas basados en Unix. Configurar un CronTrigger implica el uso de cadenas específicas compuestas por siete sub-expresiones que detallan la programación exacta. Estas sub-expresiones se separan por un espacio en blanco y representan:
1. Segundos
2. Minutos
3. Horas
4. Día del mes
5. Mes
6. Día de la semana
7. Año (campo opcional)
Entendiendo las Sub-Expresiones de Cron
Una cron-expresión completa podría ser "0 0 12 * * WED", lo que significa "todos los miércoles a las 12:00:00 pm". Cada sub-expresión puede incluir rangos y listas. Por ejemplo, el campo del día de la semana podría ser "MON-FRI" para indicar de lunes a viernes, o "MON, WED, FRI" para lunes, miércoles y viernes.
Caracteres Comodín y Valores Válidos
- '*': Representa "todos" los valores posibles de un campo. Por ejemplo, '*' en el campo de mes significa "cada mes".
- Rangos: Se pueden definir como "1-5" para los días de la semana, indicando de lunes a viernes.
- Listas: Se pueden definir como "MON,WED,FRI".
- Valores válidos:
- Segundos y Minutos: 0-59
- Horas: 0-23
- Día del mes: 1-31
- Mes: 1-12 (o JAN-DEC)
- Día de la semana: 1-7 (1=domingo) o SUN-SAT
Caracteres Especiales
- '/': Indica incrementos en los valores. Por ejemplo, '0/15' en el campo de minutos significa "cada 15 minutos a partir del minuto cero".
- '?': Especifica "sin valor específico" para los campos de día de la semana y día del mes.
- 'L': Indica "último" para los campos de día de la semana y día del mes. Por ejemplo, 'L' en el campo de día del mes significa "el último día del mes". Este caracter L es la manera corta de indicar "Last"
- 'W': Especifica el día de la semana más cercano a un día dado. '15W' significa "el día de la semana más cercano al día 15 del mes".
- '#': Define "el enésimo" día de la semana del mes. '6#3' significa "el tercer viernes de cada mes".
Ejemplos de expresiones Cron
1 - Expresión para crear un disparador (Trigger) que simplemente se dispara cada 5 minutos
"0 0/5 ***?"
2 - Expresión para crear un disparador que se activa cada 5 minutos, a los 10 segundos después del minuto (es decir, 10:00:10 am, 10:05:10 am, etc.)
"10 0/5 ***?"
3 - Expresión para crear un disparador que se activa a las 10:30, 11:30, 12:30 y 13:30, todos los miércoles y viernes.
"0 30 10-13? * WED,FRI"
4 - Expresión para crear un disparador que se activa cada media hora entre las horas de 8 am y las 10 am los días 5 y 20 de cada mes. Tenga en cuenta que el disparador no se dispara a las 10:00 am, justo a las 8:00, 08:30, 09:00 y 09:30
"0 0/30 8-9 5,20 *?"
5 - Expresión para cumplir con el requerimiento:
Ejecutar el trabajo de lunes a viernes a las 6 am y 2 pm.
"0 0 6,14 * * MON, TUE, WED, THU, FRI"
6- Expresión para cumplir con el requerimiento:
Ejecutar el trabajo los sábados: a las 6 am y 10 am.
"0 0 6,10 * * SAT"
Consideraciones Adicionales
Para programaciones más complejas, puede ser necesario utilizar múltiples disparadores (triggers). Por ejemplo, para ejecutar una tarea cada 5 minutos entre las 9 am y 10 am, y cada 20 minutos entre la 1 pm y 10 pm, se necesitarían dos triggers distintos.
Conclusión
Quartz.NET combinado con Spring.NET ofrece un marco poderoso para programar y ejecutar tareas. Al aprovechar la flexibilidad de las expresiones Cron y la solidez de la inyección de dependencias de Spring.NET, puede crear soluciones de programación escalables y mantenibles para sus aplicaciones. Este enfoque no sólo mejora la productividad sino que también garantiza un control preciso sobre la ejecución de tareas, lo que lo convierte en una herramienta esencial para cualquier desarrollador de .NET.
Para una mayor personalización y escenarios de programación más avanzados, consulte la documentación completa proporcionada por Quartz.NET y Spring.NET.
¡Gracias por llegar hasta aquí!
Espero que hayas encontrado este artículo útil y enriquecedor. Si consideras que esta información puede ser valiosa para tus contactos, te invito a compartirla en tus redes sociales. Tu apoyo me ayuda a llegar a más personas y a continuar ofreciendo contenido de calidad.
Si tienes alguna duda o comentario, estaré encantado de escucharte. Puedes ponerte en contacto conmigo directamente a través del mecanismo de contacto. Estaré atento a tus mensajes y responderé a la brevedad posible.
¡Gracias por tu apoyo y confianza!