Planification au printemps avec Quartz

1. Vue d'ensemble

Dans ce tutoriel, nous allons créer un simple Scheduler dans Spring avec Quartz .

Nous commencerons par un objectif simple: configurer facilement un nouveau travail planifié.

1.1. Composants clés de l'API Quartz

Quartz a une architecture modulaire. Il se compose de plusieurs composants de base qui peuvent être combinés selon les besoins. Dans ce didacticiel, nous nous concentrerons sur ceux qui sont communs à chaque travail: Job , JobDetail , Trigger et Scheduler .

Bien que nous utilisions Spring pour gérer l'application, chaque composant individuel peut être configuré de deux manières: la manière Quartz ou la manière Spring (en utilisant ses classes de commodité).

Nous couvrirons les deux autant que possible par souci d'exhaustivité, mais l'un ou l'autre peut être adopté. Commençons à construire, un composant à la fois.

2. Job et JobDetail

2.1. Emploi

L'API fournit une interface Job ayant une seule méthode - exécuter. Il doit être implémenté par la classe qui contient le travail réel à effectuer, c'est-à-dire la tâche. Lorsque le déclencheur d'un travail se déclenche, le planificateur appelle la méthode d' exécution , en lui passant un objet JobExecutionContext .

Le JobExecutionContext fournit l'instance de travail avec des informations sur son environnement d'exécution, y compris une poignée au planificateur, une poignée à la détente et du travail JobDetail l'objet.

Dans cet exemple rapide - le travail délègue la tâche à une classe de service:

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

2.2. JobDétail

Alors que le travail est le cheval de bataille, Quartz ne stocke pas une instance réelle de la classe de travail. Au lieu de cela, nous pouvons définir une instance du Job en utilisant la classe JobDetail . La classe du travail doit être fournie au JobDetail afin qu'il connaisse le type du travail à exécuter.

2.3. Quartz JobBuilder

Quartz JobBuilder fournit une API de type générateur pour la construction d' entités JobDetail .

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

2.4. Job de printempsDétailFactoryBean

JobDetailFactoryBean de Spring fournit une utilisation de style bean pour la configuration des instances JobDetail . Il utilise le nom du bean Spring comme nom du travail, sauf indication contraire:

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

Une nouvelle instance de JobDetail est créée pour chaque exécution du travail. L' objet JobDetail transmet les propriétés détaillées du travail. Une fois l'exécution terminée, les références à l'instance sont supprimées.

3. Déclenchement

Un Trigger est le mécanisme de planification d'un Job , c'est-à-dire qu'une instance de Trigger «déclenche» l'exécution d'un Job . Il existe une séparation claire des responsabilités entre le Job (notion de tâche) et le Trigger (mécanisme de planification).

En plus de Job , le déclencheur a également besoin d'un type qui peut être choisi en fonction des exigences de planification.

Disons de Let, nous voulons planifier notre tâche d'exécuter toutes les heures, indéfiniment - nous pouvons utiliser Quartz TriggerBuilder ou de printemps SimpleTriggerFactoryBean de le faire.

3.1. Quartz TriggerBuilder

TriggerBuilder est une API de type constructeur pour la construction de l' entité 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 fournit une utilisation de style bean pour configurer SimpleTrigger . Il utilise le nom du bean Spring comme nom du déclencheur et utilise par défaut une répétition indéfinie, sauf indication contraire:

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

4. Configuration du JobStore

JobStore fournit le mécanisme de stockage du Job et du Trigger , et est responsable de la gestion de toutes les données pertinentes pour le planificateur de jobs . L'API prend en charge les magasins en mémoire et persistants .

4.1. JobStore en mémoire

Par exemple, nous utiliserons le RAMJobStore en mémoire qui offre des performances ultra -rapides et une configuration simple via quartz.properties :

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

L'inconvénient évident du RAMJobStore est qu'il est de nature volatile . Toutes les informations de planification sont perdues entre les arrêts. Si les définitions de travail et les planifications doivent être conservées entre les arrêts, le JDBCJobStore persistant doit être utilisé à la place.

Pour activer un JobStore en mémoire au printemps , nous définissons cette propriété dans notre application.properties :

spring.quartz.job-store-type=memory

4.2. JobStore 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).