← Zur Uebersicht

EJB Timer Service

Beispiel: PolicyRenewalTimerBean.

Der EJB Timer Service ist der eingebaute Scheduling-Mechanismus des Containers - kein Quartz, kein cron, kein externer Scheduler noetig. Er kennt zwei grundverschiedene Timer-Arten, die PolicyRenewalTimerBean bewusst beide zeigt.

Automatische (Kalender-)Timer: @Schedule

@Schedule(dayOfWeek = "Mon-Fri", hour = "2", minute = "0", persistent = true,
        info = "novaris-nightly-renewal-check")
public void runNightlyRenewalCheck() { ... }

Ein @Schedule-Timer wird beim Deployment der Anwendung automatisch angelegt - niemand muss ihn zur Laufzeit explizit erzeugen. Das Kalenderausdrucksformat (dayOfWeek/hour/minute/... , angelehnt an cron) erlaubt komplexe wiederkehrende Zeitplaene direkt als Annotation. persistent = true (der Default) bedeutet: der Timer uebersteht einen Server-Neustart - der Container persistiert die Timer-Definition selbst (bei einem Cluster typischerweise in einer gemeinsamen Datenbank, damit der Timer nicht auf jedem Knoten gleichzeitig feuert).

Ein Bean kann beliebig viele @Schedule-Methoden haben - jede ist ein eigener, unabhaengiger automatischer Timer.

Programmatische Timer: TimerService + @Timeout

@Resource
TimerService timerService;

void scheduleRenewalReminder(Policy policy) {
    TimerConfig config = new TimerConfig(policy.getPolicyNumber(), true);
    timerService.createSingleActionTimer(delay.toMillis(), config);
}

@Timeout
void handleRenewalReminder(Timer timer) {
    String policyNumber = (String) timer.getInfo();
    // ...
}

Programmatische Timer werden zur Laufzeit dynamisch fuer ein konkretes fachliches Ereignis erzeugt - hier: fuer jede einzelne, zur Verlaengerung anstehende Police ein eigener Timer, der genau 7 Tage vor Ablauf feuert. createSingleActionTimer(...) erzeugt einen Timer, der genau einmal feuert (Alternative: createIntervalTimer(...) fuer wiederkehrende programmatische Timer).

Der info-Parameter ist der Schluessel zum Verstaendnis: Ein Bean darf laut Spezifikation nur eine einzige Methode mit der generischen @Timeout-Annotation haben - diese wird fuer alle programmatisch erzeugten Timer dieser Bean aufgerufen, egal welche Methode sie urspruenglich angelegt hat. Um trotzdem zu wissen, worum es bei einem konkreten Timer-Feuern geht, traegt jeder Timer ein beliebiges Serializable-Info-Objekt (hier: die Policennummer als String), das man in der @Timeout-Methode per timer.getInfo() wieder ausliest.

Warum beide Timer-Arten in einer Bean?

Das Zusammenspiel ist bewusst gewaehlt, weil es ein sehr reales Muster zeigt: ein Kalender-Timer als "Trigger" (jede Nacht pruefen, was ansteht) erzeugt daraufhin mehrere programmatische Timer als "Einzelaktionen" (fuer jede betroffene Police einen eigenen, individuell terminierten Reminder). Wuerde man stattdessen versuchen, alles in einem einzigen taeglichen @Schedule-Lauf zu erledigen, muesste man selbst Buch fuehren, welche Erinnerung an welchem Tag noch aussteht - der Timer Service nimmt einem genau das ab.

Abgrenzung zu einem externen Scheduler

Ein Vorteil des EJB Timer Service gegenueber einem separaten Scheduler-Prozess: Timer- Callbacks laufen als ganz normale, containergesteuerte Transaktionen (Default: REQUIRED, es startet also automatisch eine neue Transaktion) und koennen direkt und transaktional sicher auf andere EJBs/die Datenbank zugreifen - kein zusaetzlicher Integrationsmechanismus (REST-Aufruf, Message-Queue) noetig, nur um den Scheduler an die Anwendung anzubinden.

⌂ Cockpit