Aufgaben zeitgesteuert ausführen mit Spring-TaskScheduler

Bei Geschäftsanwendungen wird häufig eine zeitgesteuerte Ausführung (Scheduling) von Aufgaben benötigt, wie beispielsweise das regelmäßige Abrufen eines Email-Kontos. Dabei unterscheidet man beim Scheduling prinzipiell zwischen zwei Arten:

  • eine Aufgabe zu einem bestimmten Zeitpunkt ausführen (z.B.: am 24.12.2012, um 18:00 Uhr)
  • Aufgaben in festgelegten zeitlichen Abständen wiederholt ausführen (z.B.: alle 20 Minuten)

Für Scheduling können wir im Java-Umfeld die Timer-Klassen (java.util.Timer + java.util.TimerTask) des JDKs benutzen oder eine Bibliothek einsetzen, wie den sehr mächtigen Quartz-Scheduler. Falls man allerdings in seiner Anwendung Spring 3 verwendet, sollte man sich auf jeden Fall dessen Möglichkeiten anschauen (Spring-Dokumentation: Task Executing and Scheduling):

  • Scheduling mit TaskScheduler und Runnable-Objekt
  • Scheduling mit Hilfe des Task-Namespaces von Spring
  • Scheduling aufgrund der Spring Scheduling-Annotations

Scheduling mit TaskScheduler und Runnable-Objekt

Für das Scheduling von Aufgaben mit den Timer-Klassen des JDKs müssen wir den auszuführenden Code in ein Runnable-Objekt kapseln:

package de.bruns.example.spring.scheduling;

import java.util.Date;

public class ReceiveMailRunnable implements Runnable {
	public void run() {
		System.out.println("Receive emails: " + new Date());
	}
}

Spring bietet das Interface TaskScheduler mit diversen Implementierungen an, um solche Runnable-Objekte auszuführen:

public interface TaskScheduler {
	ScheduledFuture schedule(Runnable task, Trigger trigger);
	ScheduledFuture schedule(Runnable task, Date startTime);
	ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
	ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
	ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
	ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

Den TaskScheduler können wir entweder im Java-Code erstellen oder in einer XML-Datei definieren. In dem folgenden Beispiel definieren wir in der XML-Datei den TaskScheduler und einen ReceiveMailService, der den TaskScheduler für das periodische Ausführen des Runnable-Objektes nutzt:

XML-Datei mit TaskScheduler und ReceiveMailService:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/task 
       http://www.springframework.org/schema/task/spring-task-3.0.xsd
       ">

	<bean id="taskSchedulerSimple" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
		<property name="poolSize" value="5" />
	</bean>

	<bean id="receiveMailSimpleService" class="de.bruns.example.spring.scheduling.simple.ReceiveMailSimpleService">
		<constructor-arg ref="taskSchedulerSimple"></constructor-arg>
	</bean>

</beans>

Java-Code des ReceiveMailServices:

package de.bruns.example.spring.scheduling.simple;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.TaskScheduler;

import de.bruns.example.spring.scheduling.ReceiveMailRunnable;

public class ReceiveMailSimpleService implements InitializingBean {

	private final TaskScheduler taskScheduler;

	public ReceiveMailSimpleService(TaskScheduler taskScheduler) {
		this.taskScheduler = taskScheduler;
	}

	public void afterPropertiesSet() {
		ReceiveMailRunnable runnable = new ReceiveMailRunnable();
		this.taskScheduler.scheduleAtFixedRate(runnable, 1000);
	}
	
	public static void main(String[] args) {
		String[] configLocations = new String[] {"timer-simple.xml"};
		AbstractApplicationContext appContext = new ClassPathXmlApplicationContext(configLocations);
		appContext.registerShutdownHook();
	}
}

Scheduling mit Hilfe des Task-Namespaces von Spring

Spring bietet für Spring-Bean-Definitionen einen eigenen Namespace an. Wenn wir den benutzen, erzeugt Spring im Hintergrund die benötigten Java-Klassen automatisch, sodass wir keine eigene Runnable-Klasse benötigen. In der folgenden XML-Datei nutzen wir den Namespace und müssen lediglich festlegen, welche Methode wann automatisch ausgeführt werden soll. Falls wir den Task-Scheduler nicht selber definieren, erzeugt Spring ebenfalls einen eigenen Task-Scheduler automatisch:

XML-Datei mit Task-Scheduler-Namespace und ReceiveMailService:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/task 
       http://www.springframework.org/schema/task/spring-task-3.0.xsd
       ">

	<bean id="receiveMailNamespaceService" class="de.bruns.example.spring.scheduling.namespace.ReceiveMailNamespaceService">
	</bean>
	
	<task:scheduled-tasks scheduler="taskSchedulerNamespace">
	    <task:scheduled ref="receiveMailNamespaceService" method="receiveMailFixedRate" fixed-rate="6000"/>
	    <task:scheduled ref="receiveMailNamespaceService" method="receiveMailCron" cron="3/6 * * * * *"/>
	</task:scheduled-tasks>
	
	<task:scheduler id="taskSchedulerNamespace"  pool-size="5" />
</beans>

Java-Code des ReceiveMailServices:

package de.bruns.example.spring.scheduling.namespace;

import java.util.Date;

public class ReceiveMailNamespaceService {

	public void receiveMailFixedRate() {
		System.out.println("Receive emails -FixedRate: " + new Date());
	}

	public void receiveMailCron() {
		System.out.println("Receive emails -TimeCron: " + new Date());
	}

}

Scheduling aufgrund der Spring Scheduling-Annotations

Noch bequemer geht es mit der Spring-Annotation @Scheduled, die wir direkt bei der auszuführenden Methode verwenden können. In der XML-Datei sorgen wir für das automatische Scannen der Java-Packages nach Spring-Komponenten, sodass unser ReceiveMailService, der mit @Service annotiert ist, automatisch erzeugt wird. Anschließend analysiert Spring die @Scheduled-Annotationen und führt die entsprechenden Methoden periodisch aus:

XML-Datei mit Anweisungen für das automatische Scannen der Java-Dateien:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/task 
       http://www.springframework.org/schema/task/spring-task-3.0.xsd
       ">

	<context:component-scan base-package="de.bruns.example.spring.scheduling.annotation" />
	
	<task:annotation-driven/>

</beans>

Java-Code des ReceiveMailServices:

package de.bruns.example.spring.scheduling.annotation;

import java.util.Date;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ReceiveMailAnnotationService {

	@Scheduled(fixedRate = 3000)
	public void receiveMailFixedRate() {
		System.out.println("Receive emails - FixedRate: " + new Date());
	}

	@Scheduled(cron="*/5 * * * * *")
	public void receiveMailCron() {
		System.out.println("Receive emails - TimeCron: " + new Date());
	}
}

Alles zusammen ausführen

Der Quellcode für das Beispiel ist bei Github verfügbar: example-spring-scheduling. Wenn wir das Projekt mit Maven gebaut und ausgeführt haben,

mvn install
mvn exec:java -Dexec.mainClass="de.bruns.example.spring.scheduling.ReceiveMailApp"

erhalten wir die folgende Ausgabe:

ReceiveMailNamespaceService-FixedRate: 27.10.2012 09:04:28
ReceiveMailAnnotationService-FixedRate: 27.10.2012 09:04:28
ReceiveMailSimpleService: 27.10.2012 09:04:29
ReceiveMailAnnotationService-TimeCron: 27.10.2012 09:04:30
ReceiveMailSimpleService: 27.10.2012 09:04:30
ReceiveMailSimpleService: 27.10.2012 09:04:31
ReceiveMailAnnotationService-FixedRate: 27.10.2012 09:04:31
ReceiveMailSimpleService: 27.10.2012 09:04:32
ReceiveMailNamespaceService-TimeCron: 27.10.2012 09:04:33
ReceiveMailSimpleService: 27.10.2012 09:04:33
ReceiveMailSimpleService: 27.10.2012 09:04:34
ReceiveMailNamespaceService-FixedRate: 27.10.2012 09:04:34
ReceiveMailAnnotationService-FixedRate: 27.10.2012 09:04:34
ReceiveMailAnnotationService-TimeCron: 27.10.2012 09:04:35
ReceiveMailSimpleService: 27.10.2012 09:04:35
ReceiveMailSimpleService: 27.10.2012 09:04:36
ReceiveMailSimpleService: 27.10.2012 09:04:37
ReceiveMailAnnotationService-FixedRate: 27.10.2012 09:04:37
ReceiveMailSimpleService: 27.10.2012 09:04:38
ReceiveMailNamespaceService-TimeCron: 27.10.2012 09:04:39
ReceiveMailSimpleService: 27.10.2012 09:04:39

Für eigene Anwendungen würde ich die Timer-Klassen des Java-SDKs nicht mehr direkt verwenden, sondern die Funktionen von Spring nutzen. Insbesondere die auf Annotationen basierende Konfiguration finde ich sehr elegant. Der Code ist übrigens auch im Tomcat und Jetty innerhalb einer Webanwendung lauffähig.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s