Introduction au quartz

1. Vue d'ensemble

Quartz est un cadre de planification de travaux open source entièrement écrit en Java et conçu pour être utilisé à la fois dans les applications J2SE et J2EE . Il offre une grande flexibilité sans sacrifier la simplicité.

Vous pouvez créer des plannings complexes pour exécuter n'importe quel travail. Des exemples sont par exemple des tâches qui s'exécutent quotidiennement, un vendredi sur deux à 19h30 ou seulement le dernier jour de chaque mois.

Dans cet article, nous examinerons les éléments pour créer un travail avec l'API Quartz. Pour une introduction en combinaison avec Spring, nous vous recommandons de planifier au printemps avec Quartz.

2. Dépendances de Maven

Nous devons ajouter la dépendance suivante au pom.xml:

 org.quartz-scheduler quartz 2.3.0 

La dernière version se trouve dans le référentiel Maven Central.

3. L'API Quartz

Le cœur du framework est le Scheduler . Il est responsable de la gestion de l'environnement d'exécution de notre application.

Pour garantir l'évolutivité, Quartz est basé sur une architecture multi-thread. Au démarrage, le framework initialise un ensemble de threads de travail qui sont utilisés par le planificateur pour exécuter des Jobs .

C'est ainsi que le framework peut exécuter plusieurs Jobs simultanément. Il repose également sur un ensemble faiblement couplé de composants de gestion ThreadPool pour gérer l'environnement des threads.

Les principales interfaces de l'API sont:

  • Scheduler - l'API principale pour interagir avec le planificateur du framework
  • Job - une interface à implémenter par les composants que nous souhaitons faire exécuter
  • JobDetail - utilisé pour définir des instances de Job s
  • Déclencheur - un composant qui détermine le calendrier selon lequel un Job donné sera exécuté
  • JobBuilder - utilisé pour créer des instances JobDetail , qui définissent des instances de Jobs
  • TriggerBuilder - utilisé pour créer des instances de Trigger

Jetons un coup d'œil à chacun de ces composants.

4. Planificateur

Avant de pouvoir utiliser le planificateur , il doit être instancié. Pour ce faire, nous pouvons utiliser l'usine SchedulerFactory :

SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler();

Le cycle de vie d' un Scheduler est limité par sa création, via un SchedulerFactory et un appel à sa méthode shutdown () . Une fois créé le planificateur interface peut être utilisée pour ajouter, supprimer, et la liste d' emploi et Triggers et effectuer d' autres opérations liées à la planification (telles que la pause d' un déclencheur).

Cependant, le planificateur n'agira sur aucun déclencheur tant qu'il n'aura pas été démarré avec la méthode start () :

scheduler.start();

5. Emplois

Un Job est une classe qui implémente l' interface Job . Il n'a qu'une seule méthode simple:

public class SimpleJob implements Job { public void execute(JobExecutionContext arg0) throws JobExecutionException { System.out.println("This is a quartz job!"); } }

Lorsque le déclencheur du Job se déclenche, la méthode execute () est appelée par l’un des threads de travail du planificateur.

L' objet JobExecutionContext qui est passé à cette méthode fournit l'instance de travail, avec des informations sur son environnement d'exécution, un handle vers le planificateur qui l'a exécuté, un handle vers le déclencheur qui a déclenché l'exécution, l' objet JobDetail du travail et quelques autres éléments .

L' objet JobDetail est créé par le client Quartz au moment où le Job est ajouté au Scheduler. C'est essentiellement la définition de l'instance de travail :

JobDetail job = JobBuilder.newJob(SimpleJob.class) .withIdentity("myJob", "group1") .build();

Cet objet peut également contenir divers paramètres de propriété pour le Job , ainsi qu'un JobDataMap , qui peut être utilisé pour stocker des informations d'état pour une instance donnée de notre classe de job.

5.1. JobDataMap

Le JobDataMap est utilisé pour contenir toute quantité d'objets de données que nous souhaitons mettre à la disposition de l'instance de travail lors de son exécution. JobDataMap est une implémentation de l' interface Java Map et propose des méthodes pratiques supplémentaires pour stocker et récupérer des données de types primitifs.

Voici un exemple de placement de données dans JobDataMap lors de la construction du JobDetail , avant d'ajouter le travail au planificateur:

JobDetail job = newJob(SimpleJob.class) .withIdentity("myJob", "group1") .usingJobData("jobSays", "Hello World!") .usingJobData("myFloatValue", 3.141f) .build();

Et voici un exemple de la façon d'accéder à ces données lors de l'exécution du travail:

public class SimpleJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); String jobSays = dataMap.getString("jobSays"); float myFloatValue = dataMap.getFloat("myFloatValue"); System.out.println("Job says: " + jobSays + ", and val is: " + myFloatValue); } }

L'exemple ci-dessus affichera «Job dit Hello World !, et val est 3.141».

Nous pouvons également ajouter des méthodes de définition à notre classe de travail qui correspondent aux noms des clés dans JobDataMap.

Si nous faisons cela, l' implémentation JobFactory par défaut de Quartz appelle automatiquement ces setters lorsque le travail est instancié, évitant ainsi d'avoir à extraire explicitement les valeurs de la carte dans notre méthode d'exécution.

6. Déclencheurs

Les objets Trigger sont utilisés pour déclencher l'exécution des Jobs .

Lorsque nous souhaitons planifier un Job , nous devons instancier un déclencheur et ajuster ses propriétés pour configurer nos exigences de planification:

Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(40) .repeatForever()) .build();

A Trigger may also have a JobDataMap associated with it. This is useful for passing parameters to a Job that are specific to the executions of the trigger.

There are different types of triggers for different scheduling needs. Each one has different TriggerKey properties for tracking their identities. However, some other properties are common to all trigger types:

  • The jobKey property indicates the identity of the job that should be executed when the trigger fires.
  • The startTime property indicates when the trigger’s schedule first comes into effect. The value is a java.util.Date object that defines a moment in time for a given calendar date. For some trigger types, the trigger fires at the given start time. For others, it simply marks the time that the schedule should start.
  • The endTime property indicates when the trigger’s schedule should be canceled.

Quartz ships with a handful of different trigger types, but the most commonly used ones are SimpleTrigger and CronTrigger.

6.1. Priority

Sometimes, when we have many triggers, Quartz may not have enough resources to immediately fire all of the jobs are scheduled to fire at the same time. In this case, we may want to control which of our triggers gets available first. This is exactly what the priority property on a trigger is used for.

For example, when ten triggers are set to fire at the same time and merely four worker threads are available, the first four triggers with the highest priority will be executed first. When we do not set a priority on a trigger, it uses a default priority of five. Any integer value is allowed as a priority, positive or negative.

In the example below, we have two triggers with a different priority. If there aren't enough resources to fire all the triggers at the same time, triggerA will be the first one to be fired:

Trigger triggerA = TriggerBuilder.newTrigger() .withIdentity("triggerA", "group1") .startNow() .withPriority(15) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(40) .repeatForever()) .build(); Trigger triggerB = TriggerBuilder.newTrigger() .withIdentity("triggerB", "group1") .startNow() .withPriority(10) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(20) .repeatForever()) .build();

6.2. Misfire Instructions

A misfire occurs if a persistent trigger misses its firing time because of the Scheduler being shut down, or in case there are no available threads in Quartz’s thread pool.

The different trigger types have different misfire instructions available. By default, they use a smart policy instruction. When the scheduler starts, it searches for any persistent triggers that have misfired. After that, it updates each of them based on their individually configured misfire instructions.

Let's take a look at the examples below:

Trigger misFiredTriggerA = TriggerBuilder.newTrigger() .startAt(DateUtils.addSeconds(new Date(), -10)) .build(); Trigger misFiredTriggerB = TriggerBuilder.newTrigger() .startAt(DateUtils.addSeconds(new Date(), -10)) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withMisfireHandlingInstructionFireNow()) .build();

We have scheduled the trigger to run 10 seconds ago (so it is 10 seconds late by the time it is created) to simulate a misfire, e.g. because the scheduler was down or didn't have a sufficient amount of worker threads available. Of course, in a real-world scenario, we would never schedule triggers like this.

In the first trigger (misFiredTriggerA) no misfire handling instructions are set. Hence a called smart policy is used in that case and is called: withMisfireHandlingInstructionFireNow(). This means that the job is executed immediately after the scheduler discovers the misfire.

The second trigger explicitly defines what kind of behavior we expect when misfiring occurs. In this example, it just happens to be the same smart policy.

6.3. SimpleTrigger

SimpleTrigger is used for scenarios in which we need to execute a job at a specific moment in time. This can either be exactly once or repeatedly at specific intervals.

An example could be to fire a job execution at exactly 12:20:00 AM on January 13, 2018. Similarly, we can start at that time, and then five more times, every ten seconds.

In the code below, the date myStartTime has previously been defined and is used to build a trigger for one particular timestamp:

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .startAt(myStartTime) .forJob("job1", "group1") .build();

Next, let's build a trigger for a specific moment in time, then repeating every ten seconds ten times:

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("trigger2", "group1") .startAt(myStartTime) .withSchedule(simpleSchedule() .withIntervalInSeconds(10) .withRepeatCount(10)) .forJob("job1") .build();

6.4. CronTrigger

The CronTrigger is used when we need schedules based on calendar-like statements. For example, we can specify firing-schedules such as every Friday at noon or every weekday at 9:30 am.

Cron-Expressions are used to configure instances of CronTrigger. These expressions consist of Strings that are made up of seven sub-expressions. We can read more about Cron-Expressions here.

In the example below, we build a trigger that fires every other minute between 8 am and 5 pm, every day:

CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger3", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?")) .forJob("myJob", "group1") .build();

7. Conclusion

Dans cet article, nous avons montré comment créer un planificateur pour déclencher un Job . Nous avons également vu certaines des options de déclenchement les plus courantes utilisées: SimpleTrigger et CronTrigger .

Quartz peut être utilisé pour créer des plannings simples ou complexes pour exécuter des dizaines, des centaines ou même plus de travaux. Plus d'informations sur le cadre peuvent être trouvées sur le site Web principal.

Le code source des exemples peut être trouvé à l'adresse over sur GitHub.