EJB3 Stateless vs Stateful, quale è la differenza?

La risposta alla domanda “quale è la differenza fra ejb stateful e stateless?” sembra evidente: gli stateful mantengono lo stato e possono essere usati per il classico carrello della spesa mentre gli stateless, non avendo uno stato, possono essere usati per implementare dei servizi. Ma siamo sicuri che la risposta è così semplice? Per quanto viene mantenuto lo stato di un ejb stateful? Per la durata della sessione web? Proviamo a fare un po’ di chiarezza in questo post.

Le annotation degli ejb3

Una delle novità della versione 3 degli ejb è il supporto alle annotation per i session bean. Per definire un ejb è necessario scrivere un’interfaccia java e una classe che la implementa. L’interfaccia può essere annotata con @local o @remote: nel primo caso il nostro ejb sarà utilizzabile solo nella stessa jvm, nel secondo anche da jvm diverse (e quindi anche da altri server o cluster). La possibilità di definire ejb remoti è una delle feature più importanti ed è molto utile nella costruzione di architetture complesse.

La classe di implementazione di un ejb può essere annotata con @stateless o @stateful, vediamo le differenze nel dettaglio ma prima facciamo un breve richiamo su come viene creato un ejb.

Il lookup jndi

Un ejb (così come un bean di Spring) non può essere istanziato “a mano” usando una new. Per ottenere un ejb3 dobbiamo eseguire una lookup jndi, chiedendo all’application server un nuovo oggetto. Il codice per eseguire una lookup è qualcosa di simile al seguente:

try {
      Context ctx = new InitialContext();
      MyEJBLocal audit = (MyEJBLocal) ctx.lookup("java:comp/env/audit");
} catch (NamingException e) {
      throw new EJBException(e);
}

Da notare che purtroppo il nome da passare al metodo lookup non è sempre uniforme nei vari application server, per esempio cambia passando da JBoss a Websphere.

Il metodo lookup ritorna ovviamente un oggetto che implementa l’interfaccia ejb che abbiamo definito, ma tale oggetto non sarà mai una istanza della nostra classe. L’oggetto ritornato è invece un proxy del nostro oggetto che serve all’application server per poter gestire i vari servizi messi a disposizione. Per esempio nel caso in cui un metodo sia in transazione nel metodo del proxy sarà presente un blocco try/catch con all’interno la chiamata al nostro metodo preceduto da un beginTransaction e seguito da un commit o da un rollback.

In realtà non è neanche così “semplice”: non viene ritornato un proxy del nostro oggetto, ma un oggetto stub che punta a un pool di proxy! Una rappresentazione schematica è la seguente:

Questa separazione fra stub e ejb richiamato è necessario per gli ejb remoti, in questo caso il pool di ejb è su un server e gli stub sono (probabilmente) su un altro. I due server possono avere configurazioni diverse (per esempio ci possono essere datasource a database diversi) quindi gli ejb devono necessariamente eseguiti sul server in cui sono definiti.

Ma quindi quale è questa differenza?

Dopo tutta questa spiegazione torniamo all’argomento di questo post: quale è la differenza fra ejb stateful e stateless? In entrambi i casi eseguendo un lookup jndi viene ritornato un oggetto stub e invocando un metodo su questo oggetto viene eseguito il corrispondente metodo su un ejb preso dal pool. La differenza principale è proprio sulla scelta dell’ejb dal pool, se l’ejb è stateful si ha la sicurezza che tutte le chiamate a metodi di uno stub generano chiamate sullo stesso ejb. In pratica nel caso di ejb stateful lo stub mantiene un riferimento all’ejb utilizzato in modo da riusare sempre lo stesso oggetto, eventuali dati memorizzati come campi dell’oggetto sono mantenuti fra chiamate successive. Per questo motivo un ejb stateful non può essere riusato: una volta finito il proprio ciclo di vita viene distrutto dall’application server.

Se l’ejb è stateless viene utilizzato il primo ejb libero nel pool. In questo caso il pool è mantenuto essenzialmente per motivi di performance, semanticamente ogni volta che viene invocato un metodo su uno stub di un ejb stateless potrebbe essere creato un nuovo ejb su cui invocare il metodo. Per evitare la creazione di molti oggetti viene mantenuto un pool, quando viene invocato un metodo l’ejb viene rimosso dal pool e alla fine del metodo viene rimesso nel pool per essere utilizzato in futuro.

Un ejb stateless ha un ciclo di vita diverso rispetto a una Servlet (o a un bean di Spring singleton). Una Servlet è creata una sola volta e lo stesso oggetto viene usato da più thread in parallelo, per questo non deve essere memorizzato niente nelle variabili di istanza di una servlet. Di un ejb stateless invece sono mantenute più istanze in contemporanea grazie al pool, ogni istanza viene usata sempre e solo da un thread alla volta. Per questo eventuali variabili di istanze di un ejb possono essere usati all’interno dei metodi, ovviamente tenendo conto del fatto che i dati saranno persi alla fine del metodo.

Durata dello stato di un ejb stateful

Ma quindi quale è la durata dello stato di un ejb stateful? Possono essere usati per creare un carrello di un sito web di ecommerce? Dipende da dove viene memorizzato lo stub dell’ejb ritornato dalla chiamata jndi. Nel caso in cui lo stub è in una variabile locale di un metodo quando il metodo finisce lo stub si perde e un lookup jndi successivo comporta la creazione di un nuovo ejb. Nel caso in cui si voglia memorizzare il carrello in un ejb stateful è necessario salvare il riferimento allo stub nella sessione web. Ma quindi non è meglio memorizzare i dati del carrello direttamente nella sessione? Probabilmente sì, visto che la gestione degli ejb stateful è più pesante dal punto vista computazionale.

Il carrello in un ejb stateful può essere molto utile se accediamo al nostro sistema ecommerce sia da un sito web che da una applicazione desktop o da una applicazione mobile. In questo caso usando un ejb stateful possiamo scrivere la gestione del carrello una volta sola e usarlo nei vari ambienti, anche in quelli in cui non abbiamo la sessione web.

Un esempio da provare

Per concludere vediamo un esempio interessante: creiamo un ejb con un metodo solo e mettiamo un log (o un breakpoint se siamo in debug) nel costruttore dell’ejb e all’interno del metodo. Creiamo poi una servlet che esegue due volte un lookup jndi e due chiamate al metodo. Il metodo della servlet può essere schematizzato così:

MyEjb eb1 = eseguiLookup();
eb1.esegui();
eb1.esegui();
MyEjb eb2 = eseguiLookup();
eb2.esegui();
eb2.esegui();

Eseguendo questo codice quante volte viene invocato il costruttore dell’ejb nel caso che l’ejb sia definito con l’annotation stateful? La risposta corretta è 2, ogni volta che si effettua un lookup viene creato un nuovo ejb. Contenendo uno stato gli ejb stateful non vengono riciclati.

E nel caso di stateless quante volte viene invocato il costruttore? La risposta giusta non esiste! O meglio dipende da quante chiamate in parallelo alla Servlet vengono effettuate. Nel caso di una chiamata unica viene creato un ejb unico che ogni volta viene messo nel pool e subito riestratto. Nel caso di molte chiamate in parallelo questo codice potrebbe portare anche a 4 istanziazioni dell’ejb, dipende tutto dal numero di ejb presenti nel pool al momento della chiamata al metodo.

Conclusioni

In questo post abbiamo visto nel dettaglio alcune caratteristiche degli ejb3. Come abbiamo visto non è stato così semplice trovare la risposta a una domanda che poteva sembrare banale! Se avete altre domande semplici come questa lasciate un commento e proveremo a rispondere!

Fabio Collini

Software Architect con esperienza su piattaforma J2EE e attualmente focalizzato principalmente in progetti di sviluppo di applicazioni Android. Attualmente sono nel team Android di Cynny, ci stiamo occupando dello sviluppo dell'app Morphcast. Coautore della seconda edizione di Android Programmazione Avanzata e docente di corsi di sviluppo su piattaforma Android. Follow me on Twitter - LinkedIn profile - Google+