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.

Architettura EJB3 Timer

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 passato null 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’interfaccia Timer chiamando il metodo getInfo() 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 differenza tra Session Bean Stateless e Stateful.

  • 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 dopo intervalDuration 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 dopo intervalDuration 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’interfaccia Timer, che deve essere l’unico argomento del metodo annotato con @Timeout.

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 di TimerHandler. Con questa classe è possibile salvare (leggi serializzare) un timer e riattivarlo in un secondo momento;
  • cancellare il timer corrente (cancel()).
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.

83 Posts

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+