Configuración de Spring y Quartz
Para implementar esta solución, se requiere configurar un origen de datos común, un gestor de transacciones y el planificador de Quartz en el contexto de Spring.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.app.scheduler.jobs"/>
<bean name="appDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/quartzDS"/>
</bean>
<!-- Configuración del Pool de Hilos -->
<bean name="workerThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10"/>
<property name="maxPoolSize" value="20"/>
<property name="queueCapacity" value="50"/>
</bean>
<!-- Gestor de Transacciones -->
<bean name="dbTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="appDataSource"/>
</bean>
<!-- Configuración del Scheduler Distribuido -->
<bean name="distributedScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:quartz_cluster.properties"/>
<property name="dataSource" ref="appDataSource"/>
<property name="transactionManager" ref="dbTransactionManager"/>
<property name="schedulerName" value="clusteredAppScheduler"/>
<property name="overwriteExistingJobs" value="true"/>
<property name="applicationContextSchedulerContextKey" value="appCtx"/>
<property name="jobFactory">
<bean class="com.app.scheduler.config.QuartzJobAutowireSupport"/>
</property>
<property name="triggers">
<list>
<ref bean="systemReportTrigger"/>
</list>
</property>
<property name="jobDetails">
<list>
<ref bean="systemReportJobDetail"/>
</list>
</property>
<property name="taskExecutor" ref="workerThreadPool"/>
</bean>
<!-- Detalles del Job -->
<bean name="systemReportJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.app.scheduler.jobs.SystemReportJob"/>
<property name="durability" value="true"/>
<property name="requestsRecovery" value="false"/>
</bean>
<!-- Trigger Cron -->
<bean name="systemReportTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="systemReportJobDetail"/>
<property name="cronExpression" value="0/15 * * * * ?"/>
<property name="timeZone" value="GMT+8:00"/>
</bean>
</beans>
Propiedades de Configuración de Quartz
El archivo de propiedades define el comportamiento del clúster, la persistencia en base de datos y la gestión de hilos del planificador.
org.quartz.jobStore.useProperties = true
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 5000
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.txIsolationLevelReadCommitted = true
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.instanceName = DISTRIBUTED_SCHEDULER
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
Clases de Soporte para Inyección de Dependencias
Para poder utilizar anotaciones de Spring como @Autowired dentro de las clases que implementan los jobs de Quartz, es necesario crear una fábrica personalizada que extienda de SpringBeanJobFactory.
package com.app.scheduler.config;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
public class QuartzJobAutowireSupport extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory capableBeanFactory;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.capableBeanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
Finalmente, se define el job que ejecutará la lógica de negocio. Este se conecta a un servicio externo mediante inyección de dependencias.
package com.app.scheduler.jobs;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class SystemReportJob extends QuartzJobBean {
@Autowired
private ReportingService reportingService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
reportingService.generateSystemReport();
}
}
Con esta configuración, al desplegar la aplicación en múltiples nodos, Quartz coordinará la ejecución a través de la base de datos. Si un nodo falla mientras ejecuta el job, otro nodo del clúster tomará el control en la siguiente disparada del trigger, garantizando la alta disponibilidad de las tareas programadas.