Programación en primavera con Quartz

1. Información general

En este tutorial, crearemos un programador simple en Spring con Quartz .

Comenzaremos con un objetivo simple en mente: configurar fácilmente un nuevo trabajo programado.

1.1. Componentes clave de la API Quartz

Quartz tiene una arquitectura modular. Consta de varios componentes básicos que se pueden combinar según sea necesario. En este tutorial, nos centraremos en los que son comunes a todos los trabajos: Job , JobDetail , Trigger y Scheduler .

Aunque usaremos Spring para administrar la aplicación, cada componente individual se puede configurar de dos formas: la forma Quartz o la forma Spring (usando sus clases de conveniencia).

Cubriremos ambos en la medida de lo posible en aras de la exhaustividad, pero cualquiera puede ser adoptado. Comencemos a construir, un componente a la vez.

2. Trabajo y detalle del trabajo

2.1. Trabajo

La API proporciona una interfaz de trabajo con un solo método: ejecutar. Debe ser implementado por la clase que contiene el trabajo real a realizar, es decir, la tarea. Cuando se activa el desencadenante de un trabajo, el planificador invoca el método de ejecución y le pasa un objeto JobExecutionContext .

El JobExecutionContext proporciona la instancia de trabajo con la información sobre su entorno de tiempo de ejecución, que incluye un mango al programador y un identificador para el gatillo, y el trabajo de JobDetail objeto.

En este ejemplo rápido, el trabajo delega la tarea a una clase de servicio:

@Component public class SampleJob implements Job { @Autowired private SampleJobService jobService; public void execute(JobExecutionContext context) throws JobExecutionException { jobService.executeSampleJob(); } } 

2.2. Detalles del trabajo

Si bien el trabajo es el caballo de batalla, Quartz no almacena una instancia real de la clase de trabajo. En su lugar, podemos definir una instancia del trabajo usando la clase JobDetail . La clase del trabajo se debe proporcionar al JobDetail para que sepa el tipo de trabajo que se ejecutará.

2.3. JobBuilder de cuarzo

Quartz JobBuilder proporciona una API de estilo constructor para construir entidades JobDetail .

@Bean public JobDetail jobDetail() { return JobBuilder.newJob().ofType(SampleJob.class) .storeDurably() .withIdentity("Qrtz_Job_Detail") .withDescription("Invoke Sample Job service...") .build(); }

2.4. Spring JobDetailFactoryBean

JobDetailFactoryBean de Spring proporciona un uso de estilo bean para configurar instancias de JobDetail . Utiliza el nombre del bean Spring como nombre del trabajo, si no se especifica lo contrario:

@Bean public JobDetailFactoryBean jobDetail() { JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); jobDetailFactory.setJobClass(SampleJob.class); jobDetailFactory.setDescription("Invoke Sample Job service..."); jobDetailFactory.setDurability(true); return jobDetailFactory; }

Se crea una nueva instancia de JobDetail para cada ejecución del trabajo. El objeto JobDetail transmite las propiedades detalladas del trabajo. Una vez que se completa la ejecución, se eliminan las referencias a la instancia.

3. Gatillo

Un disparador es el mecanismo para programar un trabajo , es decir, una instancia de disparador "dispara" la ejecución de un trabajo. Existe una clara separación de responsabilidades entre el trabajo (noción de tarea) y el desencadenante (mecanismo de programación).

Además de Trabajo , el disparador también necesita un tipo que se pueda elegir en función de los requisitos de programación.

Vamos a decir, queremos programar nuestra tarea a ejecutar una vez cada hora, de forma indefinida - podemos utilizar de cuarzo TriggerBuilder o de primavera SimpleTriggerFactoryBean para hacerlo.

3.1. TriggerBuilder de cuarzo

TriggerBuilder es una API de estilo constructor para construir la entidad Trigger :

@Bean public Trigger trigger(JobDetail job) { return TriggerBuilder.newTrigger().forJob(job) .withIdentity("Qrtz_Trigger") .withDescription("Sample trigger") .withSchedule(simpleSchedule().repeatForever().withIntervalInHours(1)) .build(); }

3.2. Spring SimpleTriggerFactoryBean

SimpleTriggerFactoryBean proporciona un uso de estilo bean para configurar SimpleTrigger . Utiliza el nombre del bean Spring como el nombre del disparador y por defecto es repetición indefinida, si no se especifica lo contrario:

@Bean public SimpleTriggerFactoryBean trigger(JobDetail job) { SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); trigger.setJobDetail(job); trigger.setRepeatInterval(3600000); trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); return trigger; }

4. Configuración de JobStore

JobStore proporciona el mecanismo de almacenamiento para el trabajo y el desencadenador , y es responsable de mantener todos los datos relevantes para el programador de trabajos. La API admite almacenes persistentes y en memoria .

4.1. JobStore en memoria

Por ejemplo, utilizaremos RAMJobStore en memoria que ofrece un rendimiento ultrarrápido y una configuración sencilla a través de quartz.properties :

org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

El inconveniente obvio de RAMJobStore es que es de naturaleza volátil . Toda la información de programación se pierde entre paradas. Si las definiciones de trabajo y los horarios deben mantenerse entre cierres, se debe utilizar JDBCJobStore persistente en su lugar.

Para habilitar un JobStore en memoria en Spring , establecemos esta propiedad en nuestra application.properties :

spring.quartz.job-store-type=memory

4.2. JobStore de JDBC

There are two types of JDBCJobStore: JobStoreTX and JobStoreCMT. They both do the same job of storing scheduling information in a database.

The difference between the two is how they manage the transactions that commit the data. The JobStoreCMT type requires an application transaction to store data, whereas the JobStoreTX type starts and manages its own transactions.

There are several properties to set for a JDBCJobStore. At a minimum, we must specify the type of JDBCJobStore, the data source, and the database driver class. There are driver classes for most databases, but StdJDBCDelegate covers most cases:

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.dataSource=quartzDataSource

Setting up a JDBC JobStore in Spring takes a few steps. Firstly, we set the store type in our application.properties:

spring.quartz.job-store-type=jdbc

Next, we need to enable auto-configuration and give Spring the data source needed by the Quartz scheduler. The @QuartzDataSource annotation does the hard work in configuring and initializing the Quartz database for us:

@Configuration @EnableAutoConfiguration public class SpringQrtzScheduler { @Bean @QuartzDataSource public DataSource quartzDataSource() { return DataSourceBuilder.create().build(); } }

5. Scheduler

The Scheduler interface is the main API for interfacing with the job scheduler.

A Scheduler can be instantiated with a SchedulerFactory. Once created, Jobs and Triggers can be registered with it. Initially, the Scheduler is in “stand-by” mode, and its start method must be invoked to start the threads that fire the execution of jobs.

5.1. Quartz StdSchedulerFactory

By simply invoking the getScheduler method on the StdSchedulerFactory, we can instantiate the Scheduler, initialize it (with the configured JobStore and ThreadPool), and return a handle to its API:

@Bean public Scheduler scheduler(Trigger trigger, JobDetail job, SchedulerFactoryBean factory) throws SchedulerException { Scheduler scheduler = factory.getScheduler(); scheduler.scheduleJob(job, trigger); scheduler.start(); return scheduler; }

5.2. Spring SchedulerFactoryBean

Spring's SchedulerFactoryBean provides bean-style usage for configuring a Scheduler, manages its life-cycle within the application context, and exposes the Scheduler as a bean for dependency injection:

@Bean public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job, DataSource quartzDataSource) { SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties")); schedulerFactory.setJobFactory(springBeanJobFactory()); schedulerFactory.setJobDetails(job); schedulerFactory.setTriggers(trigger); schedulerFactory.setDataSource(quartzDataSource); return schedulerFactory; }

5.3. Configuring SpringBeanJobFactory

The SpringBeanJobFactory provides support for injecting the scheduler context, job data map, and trigger data entries as properties into the job bean while creating an instance.

However, it lacks support for injecting bean references from the application context. Thanks to the author of this blog post, we can add auto-wiring support to SpringBeanJobFactory like so:

@Bean public SpringBeanJobFactory springBeanJobFactory() { AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); return jobFactory; }

6. Conclusion

That's all. We have just built our first basic scheduler using the Quartz API as well as Spring's convenience classes.

The key takeaway from this tutorial is that we were able to configure a job with just a few lines of code and without using any XML-based configuration.

The complete source code for the example is available in this github project. It is a Maven project which can be imported and run as-is. The default setting uses Spring's convenience classes, which can be easily switched to Quartz API with a run-time parameter (refer to the README.md in the repository).