Introduzione al quarzo

1. Panoramica

Quartz è un framework di pianificazione dei lavori open source scritto interamente in Java e progettato per l'uso in applicazioni J2SE e J2EE . Offre una grande flessibilità senza sacrificare la semplicità.

È possibile creare pianificazioni complesse per l'esecuzione di qualsiasi lavoro. Esempi sono ad esempio attività che vengono eseguite quotidianamente, ogni altro venerdì alle 19:30 o solo l'ultimo giorno di ogni mese.

In questo articolo, daremo uno sguardo agli elementi per creare un lavoro con l'API Quartz. Per un'introduzione in combinazione con la primavera, consigliamo di programmare in primavera con Quartz.

2. Dipendenze di Maven

Dobbiamo aggiungere la seguente dipendenza al pom.xml:

 org.quartz-scheduler quartz 2.3.0 

L'ultima versione può essere trovata nel repository Maven Central.

3. L'API Quartz

Il cuore del framework è lo Scheduler . È responsabile della gestione dell'ambiente di runtime per la nostra applicazione.

Per garantire la scalabilità, Quartz si basa su un'architettura multi-thread. Quando viene avviato, il framework inizializza una serie di thread di lavoro utilizzati dallo Scheduler per eseguire i lavori .

Questo è il modo in cui il framework può eseguire molti lavori contemporaneamente. Si basa anche su un set di componenti di gestione di ThreadPool liberamente accoppiati per la gestione dell'ambiente thread.

Le interfacce chiave dell'API sono:

  • Scheduler: l'API principale per interagire con lo scheduler del framework
  • Job - un'interfaccia che deve essere implementata dai componenti che desideriamo vengano eseguiti
  • JobDetail - utilizzato per definire le istanze di lavoro s
  • Trigger: un componente che determina la pianificazione in base alla quale verrà eseguito un determinato lavoro
  • JobBuilder: utilizzato per creare istanze di JobDetail , che definiscono istanze di Jobs
  • TriggerBuilder: utilizzato per creare istanze di trigger

Diamo un'occhiata a ciascuno di questi componenti.

4. Scheduler

Prima di poter utilizzare lo Scheduler , è necessario crearne un'istanza. Per fare ciò, possiamo utilizzare SchedulerFactory di fabbrica :

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

Il ciclo di vita di uno Scheduler è limitato dalla sua creazione, tramite SchedulerFactory e una chiamata al suo metodo shutdown () . Una volta creata, l' interfaccia Scheduler può essere utilizzata per aggiungere, rimuovere ed elencare lavori e trigger ed eseguire altre operazioni correlate alla pianificazione (come la sospensione di un trigger).

Tuttavia, lo Scheduler non agirà su alcun trigger fino a quando non sarà stato avviato con il metodo start () :

scheduler.start();

5. Lavori

Un lavoro è una classe che implementa l' interfaccia del lavoro . Ha solo un semplice metodo:

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

Quando il trigger del lavoro viene attivato, il metodo execute () viene richiamato da uno dei thread di lavoro dello scheduler.

L' oggetto JobExecutionContext passato a questo metodo fornisce all'istanza di lavoro, con informazioni sul suo ambiente di runtime, un handle per lo Scheduler che lo ha eseguito, un handle per il Trigger che ha attivato l'esecuzione, l' oggetto JobDetail del lavoro e pochi altri elementi .

L' oggetto JobDetail viene creato dal client Quartz nel momento in cui il lavoro viene aggiunto allo Scheduler. È essenzialmente la definizione dell'istanza di lavoro :

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

Questo oggetto può anche contenere varie impostazioni di proprietà per il lavoro , nonché una JobDataMap , che può essere utilizzata per memorizzare le informazioni sullo stato per una determinata istanza della nostra classe di lavoro.

5.1. JobDataMap

Il JobDataMap viene utilizzato per contenere qualsiasi quantità di oggetti di dati che vogliamo mettere a disposizione l'istanza di lavoro quando si esegue. JobDataMap è un'implementazione dell'interfaccia Java Map e dispone di alcuni metodi di convenienza aggiuntivi per la memorizzazione e il recupero di dati di tipi primitivi.

Ecco un esempio di inserimento dei dati in JobDataMap durante la creazione di JobDetail , prima di aggiungere il lavoro allo scheduler:

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

Ed ecco un esempio di come accedere a questi dati durante l'esecuzione del lavoro:

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'esempio precedente stamperà "Job dice Hello World! E val è 3.141".

Possiamo anche aggiungere metodi setter alla nostra classe di lavoro che corrisponde ai nomi delle chiavi in JobDataMap.

Se lo facciamo, l' implementazione JobFactory predefinita di Quartz chiama automaticamente quei setter quando il lavoro viene istanziato, evitando così la necessità di estrarre esplicitamente i valori dalla mappa all'interno del nostro metodo di esecuzione.

6. Trigger

Gli oggetti trigger vengono utilizzati per attivare l'esecuzione dei lavori .

Quando desideriamo pianificare un lavoro , dobbiamo creare un'istanza di un trigger e regolarne le proprietà per configurare i nostri requisiti di pianificazione:

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

In questo articolo abbiamo mostrato come creare uno Scheduler per attivare un Job . Abbiamo anche visto alcune delle opzioni di trigger più comuni utilizzate: SimpleTrigger e CronTrigger .

Quartz può essere utilizzato per creare pianificazioni semplici o complesse per l'esecuzione di dozzine, centinaia o anche più lavori. Maggiori informazioni sul framework sono disponibili sul sito web principale.

Il codice sorgente degli esempi può essere trovato su GitHub.