Vi siete mai chiesti come facciano Ryanair o Expedia a mandarvi dei reminder via mail una volta che avete acquistato un biglietto? Beh, chi conosce gli scheduler, come per esempio il cron di Linux, non ha grossi dubbi. Chi conosce la piattaforma Java Enterprise neanche! Dalla specifica EJB 2.1 infatti sono presenti dei Timer Services messi a disposizione da qualsiasi Application Server (AS) J2EE 1.4 compliant. Con la specifica EJB3 l’utilizzo dei servizi di timing si è notevolmente semplificato, come del resto l’utilizzo stesso di tutta la piattaforma Enterprise.
Timer Services: come schedulare un task
Grazie a questo servizio offerto dall’Applicatin Server, per uno sviluppatore è semplice pianificare dei task, dei processi legati a qualche evento nel tempo. Un esempio? L’estratto conto inviato trimestralmente dalla banca. Con gli EJB Timer è possibile schedulare una certa azione da eseguire una volta ad una certa data o a intervalli regolari nel tempo.
Tipologie di EJB Timer
I tipi di timer supportati sono:
- Single Action Timer
-
si tratta di processi che vengono eseguiti una sola volta nel tempo. E’ possibile specificare che il timer:
- scada ad una certa data;
- scada dopo un certo intervallo di tempo (in millisecondi) dalla sua attivazione.
Il concetto di timeout è quindi fondamentale: allo scadere del tempo specificato dal timer, l’Application Server chiama un metodo di callback annotato con
@Timeout
che implementa il task che vogliamo eseguire. - Interval Timer
-
si tratta di processi che vengono eseguiti ad intervalli regolari nel tempo. E’ possibile specificare che il timer:
- scada una prima volta ad una certa data, e scada nuovamente ad intervalli regolari (espressi in millisecondi) a partire da quella data;
- scada una prima volta dopo un certo intervallo di tempo (in millisecondi) dalla sua attivazione e scada successivamente ad intervalli regolari (sempre espressi in millisecondi).
Anche in questo caso, ad ogni timeout viene invocato il metodo di business annotato con
@Timeout
.
Implementare un Timer
Ogni tipologia di EJB, ad esclusione dei Session Bean Stateful, può implementare la logica di business da pianificare nel tempo. Basta che un EJB si registri al Timer Service del server specificando il timeout e l’eventuale intervallo tra timeout successivi: sarà l’AS a chiamare il metodo annotato con @Timeout
che dovrà essere unico all’interno della classe.
Il metodo di timeout dovrà avere la seguente firma e dovrà essere dichiarato anche nell’interfaccia dell’EJB:
@Timeout
public void timeOutMethod(Timer timer) {
...
}
L’annotazione @Timeout
sarà presente solo nell’implementazione.
L’interfaccia Timer
e l’annotazione @Timeout
sono parte dell’API Timer di EJB3. Vediamo nel dettaglio tutte le interfacce :
- TimerService
-
E’ l’interfaccia principale: un EJB tramite questa interfaccia può creare un servizio, sia a singola azione che ripetuta nel tempo, o gestire servizi già creati, tramite il metodo
getTimers()
. Come si crea un timer?-
Single Action Timer:
per creare questo tipo di timer, l’interfaccia mette a disposizione due metodi:Timer createTimer(long duration, Serializable info)
viene creato un timer che scade dopo
duration
millisecondi. Il secondo argomento è facoltativo, può essere passatonull
in modo sicuro. Se si ha necessità invece di passare un oggetto al metodo chiamato al timeout, questo deve essere serializzabile e può essere recuperato dall’interfacciaTimer
chiamando il metodogetInfo()
come vedremo a breve.Timer createTimer(Date expiration, Serializable info)
viene creato un timer che scade alla data
expiration
indicata, anche in questo caso è possibile passare un oggetto serializzabile da recuperare al timeout. Come mai è stato implementato questo tipo di passaggio dei parametri? Ricordiamo che un timer può essere istanziato da tutti gli EJB ad esclusione dei Session Bean Stateful: non è possibile quindi fare affidamento sullo stato dell’EJB per recuperare i parametri. Inoltre, per un Session Bean Stateless, non è detto che al timeout venga chiamata la stessa istanza dell’EJB che lo ha inizializzato: potrebbe accadere infatti che il timer venga creato da una certa istanza di un Session Bean Stateless e al timeout ne venga chiamata un’altra presente nel pool. Per capire meglio questo aspetto vi rimando al post sulla . -
Interval Timers:
per creare questo tipo di timer, l’interfaccia mette a disposizione due metodi molto simili al caso precedente:Timer createTimer(long initialDuration, long intervalDuration, Serializable info)
viene creato un timer che scade dopo
initialDuration
millisecondi la prima volta e le volte successive dopointervalDuration
millisecondi.Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info)
viene creato un timer che scade alla data
initialExpiration
la prima volta e le volte successive dopointervalDuration
millisecondi. - Vediamo un esempio completo:
@Stateless public class ScheduledTaskEJB implements ScheduledTaskEJBLocal { @Resource private TimerService timerService; @Override public void scheduleSingleTask(long timeout) { this.timerService.createTimer(timeout, "I'm a single acion timer"); } @Override public void scheduleIntervalTask(long timeout, long interval) { this.timerService.createTimer(timeout,interval, "I'm an interval timer"); } @Timeout @Override public void doTask(Timer timer) { logger.info(timer.getInfo()); } }
Tramite injection, l’Application Server rende disponibile l’istanza del servizio di timer nell’EJB usata da due metodi che schedulano un timer a singola azione e uno a intervalli. In entrambe i casi viene passata una stringa in fase di creazione e recuperata al timeout tramite il
getInfo()
dell’interfacciaTimer
, che deve essere l’unico argomento del metodo annotato con@Timeout
.
-
Single Action Timer:
- Timer
-
Questa interfaccia permette di accedere ad una serie di informazioni di un timer schedulato come per esempio:
- l’oggetto info passato in fase di creazione (
getInfo()
) - la prossima data in cui scadrà nuovamente (
getNextTimeout()
) - il tempo rimanente in millisecondi dalla prossima scadenza (
getTimeRemaining()
) - recuperarne l’handler (
getHandle()
), ovvero una istanza diTimerHandler
. Con questa classe è possibile salvare (leggi serializzare) un timer e riattivarlo in un secondo momento; - cancellare il timer corrente (
cancel()
).
- l’oggetto info passato in fase di creazione (
- TimedObject
-
Interfaccia ereditata da EJB 2 che ha un unico metodo:
void ejbTimeout(java.ejb.Timer timer)
se usata, sostituisce l’annotazione sul metodo di callback chiamato allo scadere del timer. Con EJB 3 non si è più obbligati ad implementare questa interfaccia.
- TimerHandle
- è un’interfaccia implementata dal container che contiene lo stato di un timer attivo. Questo serve per garantire l’affidabilità del servizio anche nel caso in cui il server non sia attivo. Essendo serializzabile infatti, ogni timer handler viene persistito in modo da poter ricostruire il timer anche nel caso in cui il server sia inattivo allo scadere del tempo. Una volta ripartito, verranno ripristinati tutti i timer attivi e verranno eseguiti quelli scaduti nel frattempo.
Timer e Transazioni
La creazione e la cancellazione di un timer avvengono in modo transazionale. Questo significa che se la transazione esegue il rollback dopo la creazione o cancellazione di un timer, la sua creazione o cancellazione viene annullata. Inoltre, dato che i timer sono asincroni rispetto al thread che li ha creati, non esiste propagazione della transazione, ma ne verrà creata sempre una nuova (come se ci fosse l’attributo REQUIRES_NEW) alla chiamata del metodo di callback. Se questa transazione fallisce o esegue il rollback, il container prova a rieseguirla almeno una volta.
Conclusioni
La necessità di avere dei task da schedulare non è puramente relegata al mondo enterprise, ma è una attività molto frequente, soprattutto in quei domini modellati da workflow. In generale, per la piattaforma Java sono disponibili ottime soluzioni open source come Quartz o commerciali come Flux che risolvono il problema. In ambiente Java Enterprise, è l’Application Server a fornire una implementazione dei servizi di scheduling (talvolta inglobando le soluzioni suddette!). Come si può notare però, l’interfaccia TimerService
non è così flessibile: se per esempio ho bisogno di pianificare una certa attività da eseguire solamente il secondo plenilunio dopo l’equinozio di primavera per gli anni bisestili divisibili per 16 se sono mancino, o sottomultipli del fattoriale di 12345 se non lo sono, forse l’interfaccia in questione è un po’ povera. Come vedremo in un prossimo post, nella piattaforma Java EE 6, la specifica EJB 3.1 introduce la creazione dei timer basata sul calendario, permettendo di semplificare notevolmente la loro configurazione in caso di necessità complesse.