EJB 3.1: l'evoluzione della specie

II parte: Valutiamo alcune innovazionidi

Dopo aver introdotto le novità principali della nuova specifica, questo mese entriamo nel merito di alcune caratteristiche. La temporizzazione e l‘invocazione asincrona, passando per la nuova modalità di packaging.

Il servizio di temporizzazione

Come si è già avuto modo di dire in passato (nella serie di articoli dedicati a EJB 2.1) la possibilità di schedulare un processo enterprise di business logic è una caratteristica molto importante e molto richiesta in una comune applicazione Java EE. Già nella specifica precedente, la presenza del servizio Timer Service, permetteva di utilizzare i servizi offerti dal container per creare operazioni a tempo. La scarsa flessibilità del sistema (che obbliga a usare esclusivamente l'approccio programmatico), ha dirottato l'attenzione degli sviluppatori verso prodotti esterni a Java EE (forse il più noto è il prodotto open source Quartz, vedi [QRZ], o, per il mondo commerciale, il Flux).
Ecco un breve esempio di utilizzo del Timer Service secondo l'implementazione di EJB 3.0; in questo caso l'esempio riporta la classica implementazione di un metodo per il controllo balistico di un satellite spaziale (del resto, chi non ha mai scritto software per l'agenzia spaziale?):

@Stateless
public class SpaceManagerBean implements SpaceManager{
    @Resource TimerService timerService;
    public void checkSatellitePosition(Satellite s) {
        // il tempo di ritardo iniziale; in questo caso 1 minuto    
        long initialDuration = 1*60*1000;
        // intervallo di tempo per i controlli: in questo caso ogni 15 secondi
        long intervalDuration=15*1000;
        // crea un timer che controlla ripetutamente la posizione del satellite
        timerService.createTimer(initialDurtion, intervalDuration, r);
    }
    @Timeout
    public void monitorSatellite(Timer timer) {
        Satellite satellite = (Satellite) timer.getInfo();
        // controlla la posizione del razzo
        satellite.getPosition();
    }
}

In questo caso il controllo viene fatto dal timer che parte con un ritardo inziale di un minuto e poi lancia un evento di scadenza ogni 15 secondi; ogni volta che il timer lancia l'evento, il metodo annotato con l'annotazione @Timeout viene eseguito.

Per una operazione così puntuale, che debba essere eseguita ogni tot millisecondi, questo approccio è plausibile. Nel caso in cui si debbano eseguire operazioni con una grana temporale più grande (esempio mandare mensilmente un impulso di riallineamento al satellite geostazionario), utilizzare l'interfaccia programmatica del Timer Service può risultare macchinoso. Usare i servizi offerti dall'application server può essere immotivatamente complesso, se quello che serve è un semplice e rudimentale timer tipo il cron Unix.

La versione 3.1 ha introdotto come prima cosa la possibilità di definire in maniera dichiarativa gli eventi di schedulazione per attivare i metodi dei vari EJB, grazie all'uso della annotazione @Schedule. Ad esempio si potrebbe immaginare di dover mandare al satellite in orbita un messaggio di risincronizzazione della posizione ogni due minuti e mezzo(notare gli asterischi per denotare tutti i giorni, tutti i mesi e tutti gli anni):

@Stateless
public class public class SpaceManagerBean implements SpaceManager {
@Schedule(second="30", minute="2", hour="*", dayOfMonth="*", month="*", year="*")
        public void sendResyncSatellitePosition() {
        // invia il messaggio al satellite
    }
}

Questa sintassi è certamente molto più espressiva e semplice da leggere. Per onestà devo dire che dovendo stilare una classifica relativa all'importanza delle modifiche e delle innovazioni della versione 3.1, questa dei timer non finirebbe nella "top five". Sicuramente, se di novità si parla, una menzione se la merita, a patto che il miglioramento sia sensibile e sia massimizzata la semplicità e la espressività delle istruzioni.

Il nuovo modo di impacchettare gli EJB

Già con la specifica 3.0, l'introduzione delle annotazioni e la drastica riduzione dell'uso dell'XML ha sensibilmente semplificato il procedimento di packaging delle applicazioni enterprise. Normalmente una applicazione Java EE che sia composta da parte web e parte EJB viene impacchettata in un file .ear che contiene a sua volta il .jar e il .war della parte web. Vari file manifest e descrittori XML contengono le istruzioni per il container su come procedere al deploy della applicazione. Questo schema, utile nel caso in cui la parte EJB debba essere condivisa anche all'esterno (ovvero ad esempio i session bean siano invocabili anche da componenti contenute in altre applicazioni web), può risultare forse eccessivamente complessa per le applicazioni più semplici, in cui gli unici fruitori dei session bean siano le componenti web contenute nel file .war della web application.
Il packaging delle web application semplificato è stato introdotto proprio per risolvere questo problema; a dire il vero se questa nuova funzionalità ha visto la luce è forse più per merito delle novità introdotte con le nuove specifiche legate ai componenti web beans e alla servlet 3.0, più che per la specifica EJB 3.1: per completezza ha comunque senso che se ne parli anche in questa sede.
Con la nuova procedura non è necessario impacchettare i bean enterprise in un archivio separato, ma semplicemente copiarli fra le altre classi della web application nella cartella WEB-INF/classes; anche l'eventuale file ejb-jar.xml verrà copiato nella cartella WEB-INF. In alternativa il file .jar contenente l'applicazione EJB può essere posizionato nella cartella WEB-INF/lib (anche se questa opzione non mi pare particolarmente utile nel caso in esame, ovvero in una applicazione semplice dove gli enterprise beans sono utilizzati solo dalla applicazione web).
Ovviamente questo implica che la web application debba essere utilizzata in un contesto non solo web (ovvero il container web deve poter supportare la parte EJB). Se questa cosa può essere risolta brillantemente con la semplice esecuzione in un application server enterprise, parallelamente si affaccia all'orizzonte la possibilità di utilizzare i cosiddetti lite-JavaEE-Containers di cui parleremo più avanti.

JNDI namespace unificato

Dato che parte EJB e parte web convivono nello stesso spazio di contenimento, una interessante conseguenza derivante da questo nuovo modo di impacchettare le applicazioni è inerente la modalità di accesso alle risorse esterne. Nel caso in cui non si usi pesantemente la iniezione diretta delle risorse (IOC) ma piuttosto si proceda al reperimento da file di risorse XML tramite JNDI, si deve sempre passare per il nome con cui viene pubblicata la risorsa. Tale nome e di conseguenza anche la risorsa, sono adesso condivisi fra parte web e parte EJB; il motivo di questa scelta è più o meno obbligatoria, dato che la parte web ha solamente un local component environment connesso alla radice JNDI java:comp/env (il fatto di avere un solo nome da cui derivare tutti i namespace è certamente una cosa comoda).
Giusto per fare un esempio, se volessimo pubblicare una connessione al database potremmo scrivere nel file web.xml

    satellite data source
    jdbc/mySatelliteDB
    javax.sql.DataSource
    Container

La procedura di ricerca può essere svolta sia in una servlet che all'interno di un EJB nel seguente modo:

// Looking up my data source.
DataSource ds = (DataSource) envCtx.lookup("java:comp/env/jdbc/mySatelliteDB");

Come accennato in precedenza, più che una variazione alla specifica EJB, è questa una modifica alle specifiche web e servlet API. Occorrerà vedere la prossima Servlet 3.0; questa qui potrebbe essere considerata solamente una prima parte: dal momento in cui l'operazione di lookup potrà essere fatta in maniera dichiarativa (con IOC) anche da dentro una servlet utilizzando il namespace condiviso, allora potremo dire che usare un namespace unificato sia una novità veramente interessante.

Invocazione asincrona di session

L'invocazione asincrona di un metodo remoto è una funzionalità molto potente e molto comoda quando si devono realizzare applicazioni modulari, con un alto livello di disaccoppiamento.

Fino ad oggi il solo modo per inserire una semantica asincrona in una applicazione era tramite l'utilizzo del framework JMS che in EJB si può gestire tramite l'utilizzo dei Message Driven Beans.

Figura 1 - Un tipico schema EJB + MDB + JMS

Sebbene il modello architetturale descritto nella figura 1 sia corretto, funzionale e funzionante, impone al programmatore la conoscenza dei dettagli tecnologici della API JMS, comprese tutte le noiose e rischiose operazioni di gestione delle eccezioni e di configurazione dell'application server: ad esempio, per poter fuzionare, la coda/topic deve essere preventivamente configurata nel container. Non è certamente questo un problema particolarmente grave, ma si tratta di aggiungere complessità anche dove non serve, se ad esempio è necessario invocare semplici e piccole funzioni asincrone.

La possibilità di invocare session bean in modo asincrono è stata introdotta con la nuova specifica tramite l'uso di un nuovo framework che lavora di nascosto e che può essere invocato tramite l'annotazione @Asynchronous annotation. Ecco un esempio

@Stateless
public class public class SpaceManagerBean implements SpaceManager {    
    @Asynchronous
    public void sendPingToSpaceShip(SpaceShip ship) {
        // mandiamo un messaggio al Maggiore Tom che si sta perdendo nello spazio
        groundControl.sendMessage("This is ground control to Mjr. Tom", ship.id);
    }
}

I fan di David Bowie avranno avuto un sussulto, (vedi [DB]) mentre i comuni programmatori semplicemente avranno notato che il metodo asincrono non nasconde nulla di particolare, tranne l'uso della annotazione @Asynchronous.
Il metodo ritorna immediatamente senza attendere la risposta: in alternativa la specifica EJB 3.1 consente di usare le funzionalità offerte dalla interfaccia java.util.concurrent.Future, introdotta con il JDK 1.5. Questa interfacca rappresenta un ipotetico puntatore al thread che esegue il metodo asincrono in parallelo, consentendo di interagire con la sua normale esecuzione. La contiene il valore risultante e restituito dalla esecuzione; l'interfaccia Future permette di eseguire comuni operazioni sul thread asincrono, come cancellarnare l'esecuzione, controllarne lo stato di esecuzione o verificare l'insorgere di eventuali eccezioni. Apriamo una parentesi su questa interfaccia.

L'interfaccia Future

Si tratta di un'interfaccia che compare a partire da Java 1.5 per occuparsi delle computazioni asincrone. In pratica, tramite i suoi metodi, è possible controllare se la computazione asincrona in questione sia stata completata, o se sia ancora in corso; oppure si può attendere il termine della computazione (bloccando nel contempo la chiamata fino al completamento delle operazioni) e recuperare poi il risultato che deriva dal calcolo. Certo, si può usare l'interfaccia Future anche quando si voglia semplicemente vedere se un compito è stato completato oppure no. Ma è possibile anche usarla per annullare un task in corso quando non ci interessa che restituisca un risultato.
Ecco i metodi appartenenti a questa interfaccia.

boolean cancel(boolean mayInterruptIfRunning)

È il metodo che cancella il task nel caso esso sia ancora in corso. Una volta che il task sia stato cancellato, il metodo restituisce "true", mentre restituisce "false" se il task era già stato completato o anche se il tentativo di annullare l'operazione fallisce per qualsivoglia ragione. Se il task non era ancora partito quando viene chiamato questo metodo, allora il task non verrà mai fatto partire. Il parametro "mayInterruptIfRunning" viene usato per determinare se va interrotto o meno anche il thread che sta eseguendo il task in questione: impostandolo a "true" il thread sarà interrotto, altrimenti al task già in corso viene permesso di concludersi e il tentativo di cancellare il task fallisce.

boolean isCancelled()

Viene restituito "true" se il task è stato cancellato prima del suo completamento.

boolean isDone()

Restituisce "true" quano il task è stato concluso. "Concluso" significa che il task è stato completato seguendo il suo "corso naturale", ma anche che si è concluso per qualche eccezione oppure per l'annullamento del task dovuto a un utilizzo del metodo cancel().

V get()

Se il task è stato completato, il metodo restituirà il risultato dell'operazione. Se il task non è stato ancora completato, questo metodo attende che il task sia completato e poi restituisce il suo risultato. Questo metodo può lanciare tre eccezioni

  1. CancellationException: quando il task sia stato cancellato tramite il metodo cancel();
  2. ExecutionException: quando si sia verificata qualche eccezione durante l'esecuzione del task;
  3. InterruptedException: quando il thread che eseguiva tale task viene interrotto mentre si trova ancora nello stato di attesa.

V get(long timeout, TimeUnit timeUnit)

Come appare chiaro dal tipo di parametro, si tratta di una variante del get "con scadenza". In pratica il metodo attenderà per il tempo indicato: se nel frattempo il task giunge a completamento, allora verrà restituito il risultato della computazione. Se il tempo previsto scade senza che il task finisca, viene restituita una eccezione TimeoutException. Questo metodo può lanciare anch'esso le tre eccezioni del get appena visto sopra.

L'uso di tale interfaccia ci permette di modificare l'esempio precedente, sempre per la gioia dei fan di David Bowie:

@Stateless
public class public class SpaceManagerBean implements SpaceManager {    
    @Asynchronous
    public Future sendPingToSpaceShip(SpaceShip ship) {
        // mandiamo un messaggio al Maggiore Tom che si sta perdendo nello spazio
        SpaceMessage message = new SpaceMessage("This is ground control to Mjr. Tom");
        SpaceMessage reply = groundControl.sendPing(message, ship.id);
        return new AsyncResult(reply);
    }
}

In questo caso si restituisce javax.ejb.AsyncResult, istanza concreata di Future: il client invocante potrà ricevere il messaggio di risposta dal Mjr. Tom, quando il messaggio, inviato dalla navicella spaziale, arriverà alla stazione spaziale. L'invocazione asincrona supporta alcune funzionalità ulteriori, come il delivery guarantees e la gestione della transazionalità distribuita. Maggiori informazioni si possono trovare presso la documentazione ufficiale del JDK 5 (si veda la bibliografia [IF]).

Riferimenti

[EJB-TS] Giovanni Puliti, "EJB 2.1: Il Timer Service e Timer Beans", MokaByte 72, Marzo 2003
http://www.mokabyte.it/2003/03/ejb21-1.htm

[QRZ] Quartz
http://www.opensymphony.com/quartz/

[IF] Interfaccia Future
http://java.sun.com/javase/6/docs/api/java/util/concurrent/Future.html

[DB] Space Oddity lyrics
http://www.lyricsfreak.com/d/david+bowie/space+oddity_20036711.html

 

 

 

 

Condividi

Pubblicato nel numero
136 gennaio 2009
Giovanni Puliti lavora come consulente nel settore dell’IT da oltre 20 anni. Nel 1996, insieme ad altri collaboratori crea MokaByte, la prima rivista italiana web dedicata a Java. Da allora ha svolto attività di formazione e consulenza su tecnologie JavaEE. Autore di numerosi articoli pubblicate sia su MokaByte.it che su…
Articoli nella stessa serie
Ti potrebbe interessare anche