EJB 3.1: l‘evoluzione della specie

I parte: Una panoramica sulle novitàdi

La versione 3.0 della specifica EJB ha rappresentato una piccola rivoluzione copernicana, tanto da stravolgere completamente il modello dei componenti remoti e di persistenza. Quella che esce adesso, la 3.1, sebbene non contenga novità altrettanto strabilianti, per certi versi è forse più importante perché potrebbe essere considerata il preludio di un nuovo modo di realizzare applicazioni Java EE.

Da antico appassionato della specifica Enterprise JavaBeans (in effetti ognuno ha le proprie deformazioni mentali), ogni volta che viene presentata una nuova modifica mi affretto a leggere di cosa si tratta e quali possano essere gli impatti delle novità introdotte: nel caso della versione 3.1 della specifica EJB, sono rimasto da un lato estremamente interessato e affascinato, dall'altro stupito di come certe cose non fossero state già introdotte in passato.

Presumibilmente molte delle novità potevano essere inserite direttamente nella 3.0, anche se forse i tempi non erano maturi per un numero così alto di cambiamenti. Alcune delle nuove caratteristiche, infatti, sono oggi considerate compatibili con lo scenario enterprise, proprio perche' tale piattaforma si sta evolvendo e stanno nascendo nuove realtà. Un po' come avvenne ai tempi di Hibernate e Spring (che hanno dato impulso alla filosofia POJO-oriented di EJB), nuovi attori apparsi sul palcoscenico hanno dato impulso a questa ventata di nuove caratteristiche (si pensi a JBoss Seam, alla nuovissima specifica Web Beans, ma anche a JSF).

È presumibile che molto debba essere fatto ancora (anche a giudicare da quanto sono attivi i forum e i blog che parlano di Java EE, programmazione distribuita e persistenza): personalmente ho trovato questa 3.1 la naturale evoluzione e forse il completamento di quanto presentato in precedenza.

Le novità introdotte

Qui di seguito sono elencate le novità più interessanti introdotte con la nuova specifica:

  1. Interfacce locali opzionali: con la nuova specifica, per definire un session con interfaccia locale, non è più necessario passare per la definizione della sua interfaccia. L'uso delle annotazioni è sufficiente per creare un session bean specificando i metodi che verranno esposti come locali. L'uso delle interfacce è comunque possibile se necessario per ragioni di design.
  2. Singleton e sincronizzazione: è stato introdotto un nuovo tipo di bean (o meglio una nuova modalità di accesso ai session bean) basata sul pattern singleton: è possibile adesso risolvere l'annoso problema (se ne parla fin dalla release 1.0 di EJB) relativo all'uso di riferimenti statici (variabili o sesion bean) in una applicazione Java EE.
  3. Timer beans: è stato migliorato e potenziato il supporto e la semantica basata sulle annotazioni per la definizione dei bean temporizzati.
  4. EJB Lite: è quella che si preannuncia essere una piccola rivoluzione ma che deve ancora dimostrare tutta la sua efficacia. Sarà possibile usare container minimali, magari pluggabili in contesti EJB-less (p.e.: inserendo o pluggando Embedded JBoss, EasyBeans in Tomcat). Questo approccio si basa anche su un nuovo modo di impacchettare le applicazioni EJB. Si parla parallelamente della possibilità di usare enterprise components in un ambiente Java SE.
  5. Invocazione asincrona: accanto a JMS e MDB Beans, è stato introdotto il supporto per l'invocazione di session beans in modalità asincrona. L'uso dell'interfaccia Future per ricevere notifica su "quello che accadrà" rievoca imbarazzanti esperimenti fatti al CERN con i conseguenti cataclismi spazio-temporali che venivano prospettati dai più catastrofisti...
  6. Standardizzazione dei nomi JNDI: non è certo una rivoluzione epocale (a confronto con le altre) ma è per molti aspetti pragmatici sicuramente una delle più richieste a gran voce. Viene da dire "come mai non ci avete pensato prima?".

Nel corso di questo e dei prossimi articoli avremo modo di approfondire meglio questi temi.

Desiderata, rumori di sottofondo

La nuova specifica non è soltanto una minor version, o banalmente una evoluzione della precedente release; da quanto si legge in giro, nei vari blog e nei rumors dalla rete, potrebbe essere solo la prima pubblicazione di un nuovo scenario enterprise che si profila all'orizzonte. La versione6 della Java EE è di prossima diffusione, e molte cose di possono già intuire: le novità della EJB 3.1 potrebbero fare da testa di ponte per un nuovo modo di intendere la piattaforma enterprise, più fruibile e semplice.

Anche sulla base di quanto letto e ascoltato in giro, negli ambiti lavorativi e sul web, per una volta violerò una precisa regola personale che mi sono autoimposto, e cercherò di esprimere una valutazione o previsione del livello di utilità dei vari aspetti presi in considerazione. Il lettore più attento capirà che ciò ha più valore di "gioco" che di una valutazione previsionale affidabile al 100%:

  1. Integrazione nativa EJB 3 e Spring: sebbene i due framework siano in sovrapposizione su alcuni aspetti, il loro utilizzo congiunto potrebbe dar vita alla cosiddetta killer-technology. Sebbene già adesso sia possibile mettere in comunicazione i due strumenti, i veri vantaggi si potranno avere solo grazie alla introduzione di un layer per l'uso nativo di EJB in Spring: BEA sta lavorando proprio a questo, con il progetto Pitchfork. Dovendo giocare a fare l'indovino, mi viene da dire che l'unione dei due mondi potrebbe essere di una portentosa utilità in una prima fase, quando si renderà necessario adattare progetti esistenti Spring alla nuova EJB 3.x o viceversa; più banalmente potrebbe essere semplicemente una mossa che permetterà di far sparire un temibile concorrente, come accaduto spesso nel mondo dell'industria. Non mi stupirei se in un successiva evoluzione si assistesse a una totale migrazione su EJB a danno di Spring: la strada intrapresa da Sun pare sia ormai quella giusta e dovrebbe farla da padrona, a meno di sconvolgenti novità dai vari prodotti terze parti (Spring appunto). Funzionalità "non essenziale".
  2. Integrazione con JAX-RS: l'altro modo di fare web services, basato sul modello REST, sta guadagnando sempre più popolarità. Vista l'elevata integrazione fra EJB e JAX-WS (la API per fare SOAP web services), c'è da aspettarsi a breve una evoluzione in tal senso. Senza troppa indecisione scelgo l'etichetta "utile", e ci metto il timbro.
  3. L'affermarsi di un nuovo modo di fare web client programming ha visto il nascere di applicazioni più ricche e interattive rispetto alle semplici web application. Le Rich Internet Application sono ormai un dato di fatto e sono fortemente basate sul protocollo HTTP. EJB al momento è del tutto slegato dal modello di invocazione request-response di tale protocollo ipertestuale: da un punto di vista strettamente architetturale si può considerare RMI di EJB una alternativa completa e valida, rendendo inutile l'uso di un ulteriore layer (quello web appunto) che funga solo da traduttore. Se ci si pensa, un web service o una servlet che wrappano un session, oltre alla semplice traduzione RMI-HTTP non servono a molto altro: anche la gestione della sessione può essere rimpiazzata da un session stateful. Attivando una visione più ampia dell'uso di questi strumenti è pur vero che, in questo momento storico, sono quanto mai importanti gli aspetti di interoperabilità, architetture di integrazione e SOA: una soluzione full-EJB non affronta e non potrebbe risolvere (dove è lo standard interoperabile e il protocollo aperto?) tali problemi, nemmeno per mezzo un nuovo meccanismo di tunneling intelligente RMI over HTTP (cosa di cui si parla molto ultimamente). Forse un utilizzo valido di una tale soluzione potrebbe vedere la parte server direttamente connessa con il lato client realizzato secondo il modello RIA: collegare EJB a Flex potrebbe essere effettivamente una cosa molto interessante. Potrei attaccare una etichetta "grandissima utilità" ma devo usare una colla "stacca-attacca". Non sono ancora convinto.

Interfacce locali non più necessarie

La rivoluzione dettata dalla versione 3.0 con il passaggio all'approccio POJO-oriented ha drasticamente semplificato lo sviluppo di applicazioni enterprise. L'uso di semplici POJO ha di fatto trasformato le complesse interfacce remote (e i relativi metodi) in semplici interfacce come da programmazione a oggetti. L'innegabile semplificazione che questo ha comportato ha aperto alla possibilità di nuovi design prima impossibili: dalla semplice definizione di un contratto per gli utilizzatori delle classi remote, a più sofisticate soluzioni OOP basate sulla creazione di gerarchie di oggetti remoti. In EJB 3.0 l'opzionalità è un concetto di base molto importante: quello che non serve non si deve scrivere o specificare.

In questa direzione, la nuova 3.1 compie un altro passo: per creare un session bean (che esponga solamente metodi locali), non è più necessario definire le interfacce remote, ma si può limitare alla definizione dell'oggetto remoto con i relativi metodi.

Questa novità può essere vista come la volontà di eliminare l'ultimo ostacolo per rendere gli EJB dei POJO a tutti gli effetti, in cui la vita out-of-container non ha alcun legame con i concetti enterprise tipici delle release precedenti. Ovviamente il discorso vale per gli oggetti con interfaccia locale, che già nella versione precedente eraconsiderata quella di default. Da tener ben presente che questa scelta è del tutto opzionale: l'uso delle interfacce potrà essere reintrodotto in ogni momento, per i motivi classici che possono spingere all'uso di interfacce comuni: design by contract, unit test, disaccoppiamento, pattern programming etc... Come avremo modo di vedere in una delle prossime puntate, questa scelta ha importanti ripercussioni anche nella interazione con la nuovissima specifica dei web beans. Ne parleremo più avanti.

Singleton session beans

La possibilità di condividere dati, variabili e comportamenti all'interno dello strato di business logic enterprise (i session bean appunto) è una delle caratteristiche da sempre molto richieste dalla comunità dei programmatori. La cronica mancanza di uno strumento apposito ha da sempre indotto a dirottare l'attenzione verso l'uso di variabili e metodi statici oppure sull'uso di differenti variazioni sul tema del pattern singleton. I più temerari spesso si sono cimentati nell'utilizzo di strategie più complesse che potessero funzionare anche in scenari distribuiti e clusterizzati: in questo contesto si sono viste soluzioni basate sull'uso di container esterni (p.e.: il servlet container) o su meccanismi di cache più o meno sofisticati (come JBoss Cache, OSCache). Sun ha sempre sconsigliato l'uso di queste tecniche perche', da un lato, non essendo thread safe (come nel caso di static), possono interferire con il meccanismo di gestione del ciclo di vita dei componenti; dall'altro, non sono transazionali (a meno di complesse soluzioni architetturali basati ad esempio su pattern come il Service Locator/Resource Provider). In ogni caso si tratta di soluzioni extra-specifica, non garantite per il loro funzionamento.

Tutti questi problemi e le relative soluzioni appartengono ormai al passato, grazie all'introduzione dei Singleton session bean. Grazie a questi nuovi componenti, il container può mantenere una unica istanza condivisa fra i vari client, istanza che comunque beneficia di tutti i servizi di supporto tipici del middleware EJB (sicurezza, transazionalità dichiarativa, invocazione remota, gestione della concurrency, dependency injection, component life-cycle callbacks).

La definizione di un bean di questo tipo, in perfetto stile EJB 3.x, è molto semplice e passa per l'uso di una annotazione. Nell'esempio che segue si immagina la realizzazione di un session bean Singleton che fornisce ID univoci agli altri bean della applicazione enterprise: si potrebbe ipotizzare di usare all'interno un motore generatore di chiavi come ad esempio uuid-key-generator.sar service offerto da JBoss; per poter essere utilizzato dal session bean, tale servizio deve essere inizializzato ad esempio ricavando dal contesto JNDI il reference a tale servizio. Si tratta di un'operazione che possiamo sbrigativamente considerare come costosa e quindi da svolgere con parsimonia, centralizzando la sua esecuzione; un Singleton è lo strumento ideale per questo compito:

@Singleton
public class UUIDSingletonGenerator {
    @PersistenceContext
    private EntityManager entityManager;
    UUIDGenerator generator;
    @PostConstruct
    private void initGUUID() {
        // ricava il riferimento a servizio di generazioni delle chiavi
        // offerto dal container (codice di fantasia)
        generator = UUIDFactory.getGenerator();
    }
    @PreDestroy
    private void destroyGUUID() {
        // rilascia il servizio
        generator.shutdow();
    }
    public String generateId() {
        return generator.getNextId();
    }
    public String getLatestGeneratedId() {
        return generator.getLastId();
    }
}

In EJB, di default tutti i metodi sono thread-safe e transazionali: ogni accesso multithread al bean è serializzato dato che si assume che tutti i metodi siano marcati con attributo transazionale REQUIRED. Tale comportamento può essere modificato con le annotazioni

@TransactionManagement
@TransactionAttribute

Ovviamente un pesante uso dell'attributo REQUIRED può portare a un generale appesantimento dell'applicazione dando luogo a conflitti sugli accessi concorrenti. Nell'esempio appena visto, si potrebbe pensare di rilassare il vincolo di serializzazione specificando un vincolo più basso (ad esempio a sola lettura) per il metodo getLatestGeneratedId(); questa cosa può essere fatta con l'uso dell'attributo @ConcurrencyAttribute:

@Singleton
public class UUIDSingletonGenerator {
    @PersistenceContext
    private EntityManager entityManager;
    UUIDGenerator generator;
    @PostConstruct
    private void initGUUID() {
        // ricava il riferimento a servizio di generazioni delle chiavi
        // offerto dal container (codice di fantasia)
        generator = UUIDFactory.getGenerator();
    }
    @PreDestroy
    private void destroyGUUID() {
        // rilascia il servizio
        generator.shutdow();
    }
    @ConcurrencyAttribute(READ_WRITE_LOCK)
    public String generateId() {
        return generator.getNextId();
    }
    @ConcurrencyAttribute(READ_LOCK)
    public String getLatestGeneratedId() {
        return generator.getLastId();
    }
}

Il grado isolamento in genere viene utilizzato per garantire il livello di correttezza senza imporre meccanismi troppo vincolanti di controllo. Per chi non avesse dimestichezza con tali concetti, ricordo brevemente che vi sono tre diversi possibili tipi di problemi che possono verificarsi:

Dirty Reads: l'applicazione legge un dato (dal database) che non è ancora stato effettivamente salvato e che potrebbe non esistere più dopo una operazione di rollback.

Unrepeateable Reads: l'applicazione legge un dato che può ancora essere modificato da un'altra transazione e alla successiva rilettura il dato è diverso.

Phantom Reads: durante una transazione, l'applicazione trova un nuovo dato nel database

Per ovviare a tali inconvenienti, si abilitano nel sistema meccanismi di controllo all'accesso sulle medesime risorse, in maniera crescente, dal meno vincolante al più stringente: si parla in questi casi di livelli di isolamento. Le specifiche Java EE supportano quattro diversi livelli di isolamento:

  • TRANSACTION_READ_UNCOMMITTED: si possono avere dirty reads, unrepeatable reads e phantom reads.
  • TRANSACTION_READ_COMMITTED: si possono avere unrepeatable reads e phantom reads
  • TRANSACTION_REPEATABLE_READ: si possono avere solo phantom reads
  • TRANSACTION_SERIALIZABLE: nessuna violazione possibile

Figura 1 - Livelli di isolamento e possibili effetti sull'accesso concorrente.

Nel caso in cui tutto il Singleton sia non concorrente, si può ricorrere alla definizione al livello di Singleton:

@Singleton
@ConcurrencyAttribute(NO_LOCK)
public class UUIDSingletonGenerator {
    ...
}

Da notare che la specifica, in alternativa all'uso delle seguenti annotazioni

@ConcurrencyAttribute(READ_LOCK)
@ConcurrencyAttribute(READ_WRITE_LOCK)
@ConcurrencyAttribute(NO_LOCK)

prevede l'uso della equivalente forma più compatta

@ConcurrencyReadLock
@ConcurrencyReadWriteLock
@ConcurrencyNoLock

Analogamente l'uso dell'annotazione (con relativi valori)

@TransactionAttribute

diventa

@TransactionRequired
@RequiresNewTranscation
@TransactionNotSupported
etc...

A parte la maggior compattezza e semplicità di scrittura, da più parti è stata mossa la critica che questo approccio porta verso una esplosione incontrollata della popolazione delle annotazioni: ma lo stesso scenario si sta presentando sia in Spring che in C#, per cui forse dovremo abituarci (e magari sfruttare questa omogeneizzazione a nostro vantaggio). Nel caso si voglia, o sia necessario, implementare una gestione manuale della concorrenza, utilizzando il container come semplice fornitore dei servizi di middleware, si può passare alla "bean managed concurrency" grazie alla annotazione @ConcurrencyManagement(BEAN), come mostrato di seguito:

@Singleton
@ConcurrencyManagement(BEAN)
public class UUIDSingletonGenerator {
    @PersistenceContext
    private EntityManager entityManager;
    UUIDGenerator generator;
    @PostConstruct
    private void initGUUID() {
        // ricava il riferimento a servizio di generazioni delle chiavi
        // offerto dal container (codice di fantasia)
        generator = UUIDFactory.getGenerator();
    }
    @PreDestroy
    private void destroyGUUID() {
        // rilascia il servizio
        generator.shutdow();
    }
    
    @ConcurrencyAttribute(READ_WRITE_LOCK)
    public synchronized String generateId() {
        return generator.getNextId();
    }
    @ConcurrencyAttribute(READ_LOCK)
    public synchronized String getLatestGneratedId() {
        return generator.getLastId();
    }
}

Si noti la reintroduzione della parola chiave "synchronized", da tempo più o meno "bandita" dalle applicazioni enterprise per ovvi motivi di interferenza con la gestione del ciclo di vita degli oggetti da parte del container: in questo caso il container non interferisce e si è liberi di gestire l'accesso concorrente come si preferisce. Per il momento la gestione manuale è abilitata solamente per i Singleton bean, ma è annunciata la migrazione a tutti gli altri componenti.

Un'ultima nota: al momento la specifica non dice nulla sull'uso dei Singleton in un contesto cluster, anche se è presumibile che le implementazioni dei vendors rendano questi componenti thread-safe.

Conclusioni

Come si è potuto vedere da questo primo articolo, le novità introdotte sono davvero molte e alcune di esse sono certamente di grande rilevanza. Rimandiamo alla prossima puntata per continuare ad approfondire le successive funzioni non ancora viste.

Riferimenti

[EJB 3.1] JSR 318: Enterprise JavaBeans 3.1

http://jcp.org/en/jsr/detail?id=318

 

 

 

Condividi

Pubblicato nel numero
135 dicembre 2008
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