I Java Enterpise Bean consentono dalla versione 3 in poi l'uso di semplici annotazioni, come già descritto in un precedente articolo introduttivo su EJB3.
In particolare iSession Bean Stateful sono EJB impiegati per memorizzare lo stato di una sessione client/server, il cui utilizzo e le cui caratteristiche generali verranno approfonditi in questo articolo.
Questi oggetti risultano indispensabili in tutte quelle circostanze in cui esiste un'interazione fra client e server costituita da uno scambio multiplo di richieste e risposte e in cui è necessario salvare lo stato di tale interazione. Un tipico esempio è costituito dalle applicazioni di e-commerce, nelle quali occorre tener conto dei prodotti che l'utente ha selezionato per l'acquisto nel corso della navigazione (il cosiddetto "carrello")
A differenza dei Session Bean Stateless, che non mantengono uno stato (per cui una stessa istanza può servire richieste provenienti da client differenti), i Session Bean Stateful vengono associati alle sessioni utente: l'EJB container garantisce cioè che tutte le richieste provenienti da uno stesso client siano servite dalla stessa istanza di EJB Stateful.
Ovviamente, mantenere in memoria tutte le istanze attive è particolarmente costoso per l'Application Server, per cui è stato messo a punto un meccanismo noto come passivazione: questa consiste nel trasferire in memoria secondaria gli EJB Stateful che sono in attesa di una risposta dal client da diverso tempo, in tal modo si libera in memoria principale lo spazio per nuove istanze.
Se un EJB Stateful, in stato passivo, riceve una richiesta da parte del client ad esso associato, viene riattivato ovvero ritrasferito in memoria principale così da poterne invocare i metodi.
A partire dagli EJB 3.0 è possibile gestire le fasi di attivazione e passivazione attraverso l'uso di annotation, come vedremo in seguito.
Creazione di un Session Bean Stateful in EJB 3.0
La creazione di un Session Bean Stateful, secondo le specifiche EJB 3.0, prevede:
- la definizione di un'interfaccia business
- l'implementazione dell'interfaccia attraverso un POJO (Plain Old Java Object)
L'interfaccia contiene la definizione dei metodi esposti dall'EJB e può essere di due tipi:
- Local: quando l'EJB è accessibile localmente (all'interno dell'EJB container o del WEB container)
- Remote: quando l'EJB può essere invocato tramite RMI (Remote Method Invocation)
Nel primo caso l'interfaccia va annotata con @Local, nel secondo con @Remote
.
A differenza degli EJB stateless, gli ejb stateful non possono essere annotati con @WebService
perché i web service per definizione non hanno uno stato.
Supponiamo, ad esempio di voler definire, un EJB per la memorizzazione dei dati inseriti in un modulo organizzato su più pagine con conferma finale di salvataggio dei dati: per fare ciò è necessario un EJB Stateful che memorizzi i dati inseriti dall'utente nel passaggio da una pagina all'altra del modulo di compilazione.
Ipotizzando, ad esempio, che nella prima pagina l'utente inserirà nome e cognome, nella seconda età e numero di telefono e nella terza indirizzo e città di residenza, l'interfaccia dell'EJB risulterà costituita da quattro metodi:
package webpackage;
import javax.ejb.Local;
@Local
public interface DatiUtenteLocalInterface {
public void salvaNomeCognome(String nome, String cognome);
public void salvaEtaTelefono(int eta, String telefono);
public void salvaIndirizzoCitta(String indirizzo, String citta);
public void confermaTutto();
}
L'implementazione dell'interfaccia è costituita da un semplice POJO e deve tener conto di poche semplici regole:
- va annotata con
@Stateful
(indicando opzionalmente un attributo name) - le variabili utilizzate per mantenere lo stato della conversazione devono essere tipi primitivi o implementare l'interfaccia
Serializable
- il metodo che chiude lo scambio fra client e server e che comporta la de allocazione dell'istanza del bean, deve essere annotato con
@Remove
- i metodi che l'EJB container dovrebbe invocare dopo la costruzione e prima della distruzione dell'EJB devono essere annotati con
@PostConstruct
e@PreDestroy
- i metodi che l'EJB container dovrebbe invocare prima della passivazione e dopo la riattivazione devono essere annotati con
@PrePassivate
e@PostActivate
Ecco una possibile implementazione dell'interfaccia definita in precedenza:
@Stateful (name="DatiUtenteStateful")
public class DatiUtenteLocal implements DatiUtenteLocalInterface {
private String nome;
private String cognome;
...
public DatiUtenteLocal() {}
public void salvaNomeCognome(String nome, String cognome) {
this.nome = nome;
this.cognome = cognome;
}
...
}
(visualizza il sorgente completo)
Il metodo confermaTutto()
annotato con @Remove
è quello che conclude la conversazione fra client e server invocando un servizio del Persistence Layer che salva tutti i dati.
Al termine dell'esecuzione di tale metodo l'istanza dell'EJB viene deallocata in quanto non è più necessaria, liberando spazio in memoria per altre istanza.
Configurazione mediante deployment descriptor (ejb-jar.xml)
Alternativamente all'utilizzo delle annotazioni è possibile configurare gli EJB attraverso il deployment descriptor ejb-jar.xml
.
Il deployment descriptor consente di realizzare una completa separazione fra codice e configurazione e il suo utilizzo non è necessariamente alternativo all'utilizzo delle annotation: è buona norma infatti definire la configurazione di base nel file ejb-jar.xml ed eventualmente sovrascrivere le opzioni di configurazione mediante annotation.
All'interno del deployment descriptor tutti gli ejb vanno definiti all'interno dell'elemento <enterprise-beans>
.
I Session Bean Stateful utilizzati dall'applicazione vanno quindi definiti all'interno di tale elemento mediante degli elementi <session>
i cui sotto-elementi principali sono:
ejb-name
: il nome dell'EJBremote
: l'interfaccia remota per l'EJBlocal
: l'interfaccia locale per l'EJBejb-class
: il nome della classe che implementa il beansession-type
: il tipo di session bean (nel nostro caso stateful")remove-method
: il metodo remove per l'EJB stateful
La configurazione dei metodi che gestiscono le operazioni di passivazione/attivazione e costruzione/distruzione avviene attraverso gli elementi: <post-construct>, <pre-destroy>, <pre-passivate> e <post-activate> ciascuno dei quali deve indicare:
lifecycle-callback-class
: la classe che contiene la definizione del metodo di callbacklifecycle-callback-method
: il nome del metodo che implementa la gestione dell'operazione corrispondente.
Il deployment descriptor ejb-jar.xml
per il bean DatiUtenteStateful
potrebbe essere il seguente:
[code lang=xml]
<enterprise-beans>
…
<session>
<ejb-name>DatiUtenteStateful</ejb-name>
<local>DatiUtenteLocalInterface</local>
<ejb-class>DatiUtenteLocal</ejb-class>
<session-type>Stateful</session-type>
<remove-method>
<bean-method>
<method-name>confermaTutto</method-name>
</bean-method>
</remove-method>
…
<post-construct>
<lifecycle-callback-class>webpackage.DatiUtenteLocal</lifecycle-callback-class>
<lifecycle-callback-method>Inizializza</lifecycle-callback-method>
</post-construct>
<pre-destroy>
<lifecycle-callback-class>webpackage.DatiUtenteLocal</lifecycle-callback-class>
<lifecycle-callback-method>Distruggi</lifecycle-callback-method>
</pre-destroy>
<pre-passivate>
<lifecycle-callback-class>webpackage.DatiUtenteLocal</lifecycle-callback-class>
<lifecycle-callback-method>prePassivazione</lifecycle-callback-method>
</pre-passivate>
<post-activate>
<lifecycle-callback-class>webpackage.DatiUtenteLocal</lifecycle-callback-class>
<lifecycle-callback-method>postAttivazione</lifecycle-callback-method>
</post-activate>
…
</session>
…
</enterprise-beans>
[/code]
Utilizzo di un Session Bean Stateful e Dependency Injection (DI)
L'utilizzo di un EJB alll'interno di un componente deployato all'interno del web container (servlet o backing bean), o dell'ejb container (altro EJB) può avvenire mediante dependency injection: attraverso la dependency injection l'application server provvede ad inizializzare le risorse di cui un componente ha bisogno per svolgere il suo lavoro.
Per raggiungere tale proposito si fa uso (nel caso di un contesto locale) dell'annotation @EJB
, la quale può definire opzionalmente i seguenti attributi:
name
: il nome attraverso il quale viene fatto il look up dell'EJB nell'ambientebeanInterface
: il tipo di interfaccia implementata dall'EJB referenziatobeanName
: il nome indicato come attributo name dell'annotation @StatefulmappedName
: specifica il jndi name globale dell'EJB referenziato.description
: una descrizione dell'EJB referenziato
Supponendo di far uso di Java Server Faces (JSF) nel View Layer, il nostro Session Bean Statefull verrà configurato e invocato da un backing-bean così definito:
package webpackage;
import webpackage.DatiUtenteLocalInterface;
public class ViewBean {
private static final long serialVersionUID = 1L;
private String nome;
private String cognome;
private int eta;
private String telefono;
private String indirizzo;
private String citta;
@EJB (beanName="DatiUtenteStateful")
private DatiUtenteLocalInterface datiUtenteStateful;
public String passo1() {
datiUtenteStateful.salvaNomeCognome(nome, cognome);
return "pagina2";
}
public String passo2() {
datiUtenteStateful.salvaEtaTelefono(eta, telefono);
return "pagina3"
}
public String passo3() {
datiUtenteStateful.salvaIndirizzoCitta(indirizzo, citta);
return "pagina_conferma";
}
public String conferma() {
datiUtenteStateful.confermaTutto();
return "operazione_completata";
}
}
passo1(), passo2(), passo3() e conferma() sono i metodi del backing bean invocati nel passaggio da una pagina all'altra: questi invocano il metodo del Session Bean Stateful iniettato mediante l'annotation @EJB e restituiscono gli outcome (String) che vengono utilizzati dal modello di navigazione in JSF (la cui descrizione prescinde dallo scopo di questo articolo e per la quale chi fosse interessato può approfondire leggendo l'articolo di introduzione su JSF).
I valori inseriti dall'utente nelle varie pagine del modulo saranno infine disponibili direttamente nelle proprietà del backing bean perché configurati attraverso binding.
Ovviamente gli esempi proposti costituiscono solo uno schema generale e per poter eseguire classi EJB3 c'è bisogno di effettuare il deploy in un application server in grado di effettuarne il deploy (ad esempio jboss) o di introdurre librerie aggiuntive per i container che non supportassero queste funzionalità (ad esempio OpenEJB per Apache Tomcat).