EJB3 Timer Services: il senso del tempo per gli EJB

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 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 .

  • 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.

Andrea Como

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 . - -

  • roronoa80

    Ciao a tutti
    Cercherò di essere il più chiaro possibile e anche sintetico ma credo che inevitabilmente mi dovrò dilungare un po’.
    Mi sto interessando agli EJB, in particolare a quelli “timer”, perchè voglio rendere più robusta ma soprattutto scalabile una mia applicazione, inizio a spiegare.
    Ho un applicazione che deve compiere diversi task temporizzati, al momento tutto funziona ma il punto è che è realizzato in maniera piuttosto grezza, tutti il codice è incluso in un unico war, e ogni classe utilizza TimerTask per eseguire i task temporizzati.
    Come detto ora questo non mi basta più, voglio rendere la mia applicazione modulare per permetterne la scalabilità così mi sto interessando (per la prima votla purtroppo) agli EJB, il mio intento è quello di mappare ogni mio task temporizzato con un EJB timer (includendogli la logica dei singoli job) in modo da renderli deployabili separatamente e, a limite, anche su macchine diverse (ma questo lo tralascio per il momento, mi basta modularizzare il war per ora)
    Dunque quello che ora è un singolo war dovrà diventare, nelle mie intenzioni, un war (più snello) con tanti Ejb, uno per ogni tipologia di task che devo eseguire, ognuno con un suo timer.
    Ora i problemi sono:
    Il codice degli Ejb e del war sonmo fortemente legati, nel senso, che non solo il war deve poter chiamare metodi degli Ejb (cosa che dalla documentazione che leggo non dovrebbe essere un problema, ma ancora non ci ho provato) ma anche il codice degli Ejb dovrebbe poter chiamare dei metodi di una classe all’interno del war.
    In pratica vorrei ottenere una situazione in cui si crei una sorta di struttura a stella in cui ci sia una classe, all’interno del war, come centro della struttura presso la quale tutti gli ejb (di numero indefinito) si registrano e con la quale ci possa essere una comunicazione bidirezionale fra questi componenti.
    Ora come dicevo non riesco a trovare niente che mi spieghi come (anche se da alcuna documentazione che ho letto sembra possibile) un Ejb possa chiamare un metodo di una normale classe situata all’interno di un altro war.
    Concludendo vi chiedo:
    mi sto muovendo nella giusta direzione per ottenere la scalabilità del mio progetto?
    Ha senso che un classe mantenga un riferimento ad un Ejb il cui ciclo di vita è gestito da un container?
    dovrei forse fare in modo che gli Ejb funzionino solo da trigger per le operazioni che restano comunque all’interno del war? ma in tal caso la tanta decantata scalabilità e modularità dove sta?
    In pratica potrei riassumere che il problema grosso è capire come gli Ejb possano comunicare con altri componenti (classi) deployati sullo stesso AS ma in package diversi (se qualcuno conosce OSGI in pratica vorrei ottenere un risultato simile, con OSGI è molto più semplice in quanto questa piattaforma è particolarmente orientata a queste problematiche ma con javaEE e i suoi Ejb, che ribadisco di non conoscere, sto trovando molte difficoltà).
    Ringrazio in anticipo chiunque mi dia una mano
    Ciao

    • cosenonjaviste

      Ciao,
      se hai intenzione di lavorare con gli EJB ti consiglio di usare un ambiente di runtime come jboss o glassfish in modo da avere “tutto pronto”. Dovresti poi strutturare la tua applicazione in diversi progetti in moda da avere almeno un EAR che contiene un WAR (se hai bisogno di una interfaccia web, altrimenti ne puoi fare anche a meno) e un modulo EJB (che alla fine è deployato come un JAR) dove dovresti mettere tutta la logica di business, ovvero i tuoi task. Non puoi infatti creare referenze cicliche tra modulo ejb e war e d’altronde sarebbe anche formalmente scorretto perché la logica di business dovrebbe funzionare indipendentemente dall’interfaccia che ci metti sopra. Ti consiglio di spostare quindi i tuoi task in uno o più moduli EJB, da collegare in modo gerarchico se necessario: le referenze cicliche non sono un buon design e infatti in java non si possono fare perché incorri in problemi di compilazione (sarebbe come risolvere il dilemma se è nato prima l’uovo o la gallina… non so se rende l’idea 😉 ).
      Per l’altra esigenza di rendere scalabile l’applicazione, la piattaforma Java EE ti mette a disposizione gli EJB remoti, che credo facciano al caso tuo (e che mi sa che non abbiamo mai affrontato sul blog). In questo modo puoi chiamare altri EJB contenuti in EAR differenti dentro lo stesso application server, oppure deployati in altri nodi di un cluster di application server o tramite vere e proprie chiamate remote (un po’ stile web service) verso application server separati (dove vanno opportunamente configurati i certificati per poter comunicare in sicurezza).
      Spero di aver risposto ai tuoi dubbi
      Andrea

      • roronoa80

        Ciao Andrea, innanzitutto grazie.

        La tua risposta mi è ababstanza chiara però mi risulta strana.

        Tralasciando la questione di poter deployare su macchine diverse i componenti della mia applicazione (fondamentalmente 1 war e n Ejb) mi dici che devo inserirli dentro uno stesso ear?

        Confesso che mi risulta strano….ma non sono io l’esperto qui di Ejb :P.

        Per le referenze cicliche hai perfettamente ragione, ma allora come risolvere questa cosa che 2 componenti hanno bisogno di comunicare fra loro in maniera “bidirezionale”?

        Ne approfitto poi per esporti 1 altro problema che sto incontrando.

        I miei task, quindi i futuri Ejb, sono tutti modellati da una classe astratta che implementa una ben definita interfaccia, se provo con la DI ad ottenere un riferimento del mio Ejb:

        @EJB
        public EjbInterface mioEjb;

        quale prenderà, visto che tutti gli ejb implementano la stessa interfaccia? Posso per caso fare una cosa del genere?

        @EJB
        public EjbInterface[] mioEjb;

        Sai vengo da un’esperienza con OSGI e, se conocsi anche tu questa tecnologia, volevo in pratica riprodurre un comportamento come per il ServiceTracker, sai come posso ottenere una cosa simile?
        Ti ringrazio tantissimo e spero tu possa continuare ad aiutarmi, del resto anche io continuerò a scrivere se troverò una soluzione a queste mie problematiche così da poter essere d’aiuto ad altri.
        Ciao

        • cosenonjaviste

          Ciao,
          cerco di spiegarmi meglio: il discorso dello stesso EAR/macchine diverse funziona in un ambiente clusterizzato. In pratica hai n istanze del server (anche su macchine diverse) che dialogano tra loro e si presentano come se fossero un unico server: tu fai il deploy dell’EAR su una (la “master” per intendersi) e l’applicazione viene propagata sugli altri server (nodi del cluster). A questo punto ti serve davanti un load balancer che si occuperà automaticamente di dirigere il traffico sul nodo meno carico. Ci sono dei plugin di apache che lo fanno, se non hai bisogno di un load balancer hardware.
          Per quanto riguarda la comunicazione bidirezionale, credo che l’unico modo sia ricorrere ai web services. A livello di compilazione non c’è modo di fare referenze cicliche.
          Per quanto riguarda invece il discorso di interfacce ejb con differenti implementazioni, mi sa che devi dare un nome JNDI ad ogni implementazione e iniettare specificatamente quella. Credo che l’application server ti dia problemi a runtime se non lo specifichi. L’iniezione di multiple istanze che suggerivi non è supportata da EJB, ma funziona per esempio con CDI. Ti consiglio di dare un’occhiata a CDI che fa parte delle novità della Java EE 6: è molto potente ed è complementare agli EJB.

          • roronoa80

            Ciao
            conosco l’argomento cluster etc., mi chiedevo se però con una giusta modularizzazione del codice si potevano raggiungere risultati simili (in passato mi sono trovato in situazioni dove parti di uno stesso progetto erano deployate su macchine diverse e comunicavano fra loro mediante code JMS) senza tirare in ballo troppe tecnologie.
            Darò un occhiata a CDI come mi hai suggerito.
            Ti ringrazio.
            Ciao