Einführung in Quarz

1. Übersicht

Quartz ist ein Open Source-Framework für die Jobplanung, das vollständig in Java geschrieben und für die Verwendung in J2SE- und J2EE- Anwendungen entwickelt wurde. Es bietet große Flexibilität ohne Einbußen bei der Einfachheit.

Sie können komplexe Zeitpläne für die Ausführung eines Jobs erstellen. Beispiele sind z. B. Aufgaben, die täglich, jeden zweiten Freitag um 19:30 Uhr oder nur am letzten Tag eines jeden Monats ausgeführt werden.

In diesem Artikel werden Elemente betrachtet, mit denen ein Job mit der Quartz-API erstellt werden kann. Für eine Einführung in Kombination mit Spring empfehlen wir die Planung im Frühjahr mit Quarz.

2. Maven-Abhängigkeiten

Wir müssen der pom.xml die folgende Abhängigkeit hinzufügen :

 org.quartz-scheduler quartz 2.3.0 

Die neueste Version finden Sie im Maven Central-Repository.

3. Die Quarz-API

Das Herzstück des Frameworks ist der Scheduler . Es ist für die Verwaltung der Laufzeitumgebung für unsere Anwendung verantwortlich.

Um die Skalierbarkeit sicherzustellen, basiert Quartz auf einer Multithread-Architektur. Beim Start initialisiert das Framework eine Reihe von Arbeitsthreads , die vom Scheduler zum Ausführen von Jobs verwendet werden .

Auf diese Weise kann das Framework viele Jobs gleichzeitig ausführen . Es basiert auch auf einem lose gekoppelten Satz von ThreadPool- Verwaltungskomponenten zum Verwalten der Thread-Umgebung.

Die wichtigsten Schnittstellen der API sind:

  • Scheduler - Die primäre API für die Interaktion mit dem Scheduler des Frameworks
  • Job - eine Schnittstelle, die von Komponenten implementiert werden soll, die wir ausführen möchten
  • JobDetail - verwendet Instanzen zu definieren Job s
  • Trigger - Eine Komponente, die den Zeitplan festlegt, nach dem ein bestimmter Job ausgeführt wird
  • JobBuilder - wird zum Erstellen von JobDetail- Instanzen verwendet, die Instanzen von Jobs definieren
  • TriggerBuilder - wird zum Erstellen von Trigger- Instanzen verwendet

Werfen wir einen Blick auf jede dieser Komponenten.

4. Scheduler

Bevor wir den Scheduler verwenden können , muss er instanziiert werden. Dazu können wir die werkseitige SchedulerFactory verwenden :

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

Ein Scheduler ‚s Lebenszyklus wird von seiner Entstehung begrenzt, über einen SchedulerFactory und einen Aufruf seiner shutdown () Methode. Nach der Erstellung kann die Scheduler- Oberfläche zum Hinzufügen, Entfernen und Auflisten von Jobs und Triggern sowie zum Ausführen anderer planungsbezogener Vorgänge (z. B. Anhalten eines Triggers) verwendet werden.

Doch der Scheduler handeln wird sich nicht auf Trigger , bis er mit dem gestartet wurde start () Methode :

scheduler.start();

5. Jobs

Ein Job ist eine Klasse, die die Jobschnittstelle implementiert. Es gibt nur eine einfache Methode:

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

Wenn der Trigger des Jobs ausgelöst wird, wird die Methode execute () von einem der Arbeitsthreads des Schedulers aufgerufen.

Das JobExecutionContext- Objekt, das an diese Methode übergeben wird, stellt der Jobinstanz Informationen zu ihrer Laufzeitumgebung, ein Handle für den Scheduler , der sie ausgeführt hat, ein Handle für den Trigger , der die Ausführung ausgelöst hat, das JobDetail- Objekt des Jobs und einige andere Elemente bereit .

Das JobDetail- Objekt wird vom Quartz-Client zum Zeitpunkt des Hinzufügens des Jobs zum Scheduler erstellt. Es ist im Wesentlichen die Definition der Jobinstanz :

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

Dieses Objekt kann auch verschiedene Eigenschafteneinstellungen für den Job sowie eine JobDataMap enthalten , mit der Statusinformationen für eine bestimmte Instanz unserer Jobklasse gespeichert werden können.

5.1. JobDataMap

Die JobDataMap wird verwendet, um eine beliebige Anzahl von Datenobjekten zu speichern, die der Jobinstanz bei der Ausführung zur Verfügung gestellt werden sollen. JobDataMap ist eine Implementierung der Java Map- Schnittstelle und verfügt über einige zusätzliche praktische Methoden zum Speichern und Abrufen von Daten primitiver Typen.

Hier ist ein Beispiel für das Einfügen von Daten in die JobDataMap beim Erstellen des JobDetail , bevor der Job dem Scheduler hinzugefügt wird:

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

Und hier ist ein Beispiel für den Zugriff auf diese Daten während der Ausführung des Jobs:

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); } }

Im obigen Beispiel wird "Job sagt Hallo Welt!, Und Wert ist 3.141" gedruckt.

Wir können unserer Jobklasse auch Setter-Methoden hinzufügen, die den Namen der Schlüssel in der JobDataMap entsprechen.

Wenn wir dies tun, ruft die Standard- JobFactory- Implementierung von Quartz diese Setter automatisch auf, wenn der Job instanziiert wird, wodurch verhindert wird, dass die Werte innerhalb unserer Ausführungsmethode explizit aus der Map abgerufen werden müssen.

6. Auslöser

Triggerobjekte werden verwendet, um die Ausführung von Jobs auszulösen .

Wenn wir einen Job planen möchten , müssen wir einen Trigger instanziieren und seine Eigenschaften anpassen, um unsere Planungsanforderungen zu konfigurieren:

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 diesem Artikel haben wir gezeigt, wie Sie einen Scheduler erstellen , um einen Job auszulösen . Wir haben auch einige der am häufigsten verwendeten Triggeroptionen gesehen : SimpleTrigger und CronTrigger .

Mit Quarz können einfache oder komplexe Zeitpläne für die Ausführung von Dutzenden, Hunderten oder sogar mehr Jobs erstellt werden. Weitere Informationen zum Framework finden Sie auf der Hauptwebsite.

Den Quellcode der Beispiele finden Sie auf GitHub.