Manchmal möchte man Ereignisse mit Java zeitgesteuert oder wiederholt ausführen. Das geht auch mit Java Mitteln. Die bekannte java.util.Timer Klasse kann schon was. Der Open Source Framework Quartz hat ua. aber diese Vorteile:
- persistence mechanismen
- flexible Zeitplanung
- Thread-Pool
- Managment Schemen
- flexibel
- fehlertolerant, auch nach Systemstart
Quartz ist ein kleiner Framework der nur quartz-x.y.z.jar im Classpath benögigt (inkl. log sl4j-api.jar, und c3p0.jar). Er kann Standalone oder im Server als J2EE Anwendung verwendet werden. Die wichtigsten drei Klassen sind Task/Job, Trigger und der Listener.
Sie spielen so zusammen:
Wie kann ein Timer in Java mit Quartz erstellt werden?
In diesem Beispiel fragen wir alle 5 Minuten eine REST Service ab, der die Anzahl aller Flugzeuge in Hannover lierfert.
Zuerst diese Abhängigkeiten in der pom.xml eintragen:
Wir brauchen nur drei Klassen.
Einen AbfrageListener.java der den JobListener implementiert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package de.wenzlaff.timer; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Der Listener für die Abfrage. * * @author Thomas Wenzlaff * @version 0.1 */ public class AbfrageListener implements JobListener { private static final Logger LOG = LoggerFactory.getLogger(AbfrageListener.class); public String getName() { return "Abfrage Listener"; } public void jobExecutionVetoed(JobExecutionContext context) { // Quartz will invoke this method when the job execution was banned from the trigger. LOG.debug("jobExecutionVetoed"); } public void jobToBeExecuted(JobExecutionContext context) { // Quartz will invoke this method when the job is going to be executed. final String jobName = context.getJobDetail().getKey().toString(); LOG.debug("jobToBeExecuted: {} Job startet ...", jobName); } public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { // Quartz will invoke this method when the job was executed. LOG.debug("jobWasExecuted"); final String jobName = context.getJobDetail().getKey().toString(); LOG.debug("Job : {} ist beendet", jobName); if (jobException != null && jobException.getMessage().equals("")) { // bei Fehler LOG.error("Exception thrown by: {}", jobName, jobException); } } } |
Eine Job Klasse AbfrageJob.java die die eigentliche Arbeit macht und den Job implementiert:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package de.wenzlaff.timer; import java.io.IOException; import java.io.StringReader; import javax.ws.rs.client.ClientBuilder; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Der Abfrage Job. * * @author Thomas Wenzlaff * @version 0.1 */ public class AbfrageJob implements Job { private static final Logger LOG = LoggerFactory.getLogger(AbfrageJob.class); // URL des Feeds aller Flugzeuge in Hannover von Thomas Wenzlaff. // private final static String FLUGZEUG_URL = "https://api.thingspeak.com/channels/44177/feeds/last.xml"; public void execute(JobExecutionContext context) throws JobExecutionException { LOG.debug("Abfrage context: {}", context); String result = ClientBuilder.newClient().target(FLUGZEUG_URL).request().get(String.class); // result get sample: // <?xml version="1.0" encoding="UTF-8"?> // <feed> // <created-at type="dateTime">2016-05-13T12:20:01Z</created-at> // <entry-id type="integer">79166</entry-id> // <field1>18</field1> // <id type="integer" nil="true"/> // </feed> String field1; try { field1 = new SAXBuilder().build(new StringReader(result)).getDocument().getRootElement().getChild("field1").getText(); } catch (JDOMException e) { throw new JobExecutionException(e); } catch (IOException e) { throw new JobExecutionException(e); } LOG.info("Aktuelle Anzahl Flugzeuge in Langenhagen: {} ", field1); } } |
Und nun noch eine Klasse Abfrage.java für die Verbindungen und den Start des Timers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package de.wenzlaff.rest.client; import org.quartz.CronScheduleBuilder; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.KeyMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.wenzlaff.timer.AbfrageJob; import de.wenzlaff.timer.AbfrageListener; /** * Beispiel REST Abfrage der Flugzeugdaten aus Hannover. * * @author Thomas Wenzlaff * @version 0.1 */ public class Abfrage { private static final Logger LOG = LoggerFactory.getLogger(Abfrage.class); /** * Start Methode. * * @param args * keine. * @throws Exception * bei Fehlern */ public static void main(String[] args) throws Exception { LOG.info("Starte Flugzeug Abfrage"); // nun ein Beispiel zum periodischen Abfragen mit Quarz final JobKey abfrageKey = new JobKey("AbfrageNamen", "AbfrageGruppe"); final JobDetail job = JobBuilder.newJob(AbfrageJob.class).withIdentity(abfrageKey).build(); // alle 5 Sekunden final Trigger trigger = TriggerBuilder.newTrigger().withIdentity("AbfrageNamen", "AbfrageGruppe").withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build(); final Scheduler scheduler = new StdSchedulerFactory().getScheduler(); // Listener mit den abfrageKey verbinden scheduler.getListenerManager().addJobListener(new AbfrageListener(), KeyMatcher.keyEquals(abfrageKey)); scheduler.start(); scheduler.scheduleJob(job, trigger); } } |
Hier die UML Übersicht.
Hier die Ausgabe, wenn das Programm gestartet wird:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
INFO Abfrage - Starte Flugzeug Abfrage INFO StdSchedulerFactory - Using default implementation for ThreadExecutor INFO SimpleThreadPool - Job execution threads will use class loader of thread: main INFO SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl INFO QuartzScheduler - Quartz Scheduler v.2.2.3 created. INFO RAMJobStore - RAMJobStore initialized. INFO QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. NOT STARTED. Currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads. Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. INFO StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties' INFO StdSchedulerFactory - Quartz scheduler version: 2.2.3 INFO QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started. DEBUG QuartzSchedulerThread - batch acquisition of 0 triggers DEBUG QuartzSchedulerThread - batch acquisition of 1 triggers DEBUG PropertySettingJobFactory - Producing instance of Job 'AbfrageGruppe.AbfrageNamen', class=de.wenzlaff.timer.AbfrageJob DEBUG QuartzSchedulerThread - batch acquisition of 1 triggers DEBUG JobRunShell - Calling execute on job AbfrageGruppe.AbfrageNamen INFO AbfrageJob - Aktuelle Anzahl Flugzeuge in Langenhagen: 13 DEBUG PropertySettingJobFactory - Producing instance of Job 'AbfrageGruppe.AbfrageNamen', class=de.wenzlaff.timer.AbfrageJob DEBUG JobRunShell - Calling execute on job AbfrageGruppe.AbfrageNamen DEBUG QuartzSchedulerThread - batch acquisition of 1 triggers INFO AbfrageJob - Aktuelle Anzahl Flugzeuge in Langenhagen: 13 DEBUG PropertySettingJobFactory - Producing instance of Job 'AbfrageGruppe.AbfrageNamen', class=de.wenzlaff.timer.AbfrageJob DEBUG JobRunShell - Calling execute on job AbfrageGruppe.AbfrageNamen DEBUG QuartzSchedulerThread - batch acquisition of 1 triggers INFO AbfrageJob - Aktuelle Anzahl Flugzeuge in Langenhagen: 13 |
Es wird alle 5 Minuten die Anzahl der Flugzeuge ausgegeben. Hier in HAJ werden im Moment nur 13 empfangen.
Das ganze Projekt kann von Github geladen werden.