Dovendo
dare una definizione concisa ed esauriente di un session bean si potrebbe
dire che si tratta di un componente manipolabile da un client secondo il
modello classico di EJB basato sulle interfacce home e remote.
E'
quindi mlot simile agli entity, ma dai quali si differisce da per
una caratteristica fondamentale: mentre gli entity rappresentano un dato
o una determinata struttura dati, i session incorporano al loro interno
una serie di funzionalità o servizi.
Spesso
si dice che sono componenti che inglobano la business logic del client
spostandola sul server remoto.
Dovrebbe
subito essere chiaro che, riconsiderando quanto visto fino ad ora per gli
entity, come il client possa interagire con il componente e quale sia lo
scenario che si prefigura: il client potrà utilizzare entity bean
per accedere a strutture dati remote, e manipolare tali informazioni, ma
anche il workflow della applicazione tramite un session bean.
Anche
per i session esistono due tipi bean, gli stateless e gli stateful: nonostante
entrambi siano stati pensati per offrire al client la possibilità
di eseguire logica lato server, si tratta di due componenti molto diversi
per scopo e funzionamento.
Come
si intuisce dal nome i primi sono componenti senza stato, mentre i secondi
consentono di memorizzare uno stato fra invocazioni successive dei
metodi remoti da parte del client. Vediamo in questo caso cosa significa
mantenimento dello stato.
Stateless beans
La
caratteristica principale di questi componenti è quella di non è
offrire nessuna forma di persistenza delle variabili remote fra due invocazioni
successive del bean da parte del componente.
In
un certo senso l’interazione che si crea fra il client ed il bean
remoto è paragonabile a ciò che accade fra un client http
(browser) quando invoca un componente CGI server side: anche in questo
caso infatti non vi è nessuna memorizzazione dello stato, se non
tramite cookie o altre tecniche analoghe.
Tali
bean quindi non dispongono di nessuna variabile o meccanismo per tenere
traccia dei dati relativi al client. Tale assunzione però è
legata esclusivamente alle variabili remote (nella terminologia RMI), ovvero
quelle accessibili al client, e non a quelle di istanza interne alla classe.
Ad esempio è possibile tramite una variabile implementare un contatore
del numero di invocazioni di un determinato metodo: ad ogni invocazione,
ogni metodo potrà incrementare tale contatore anche se non è
possibile ricavare l’identità del client invocante.
Tale
separazione fra client e bean remoto impone quindi che tutti i parametri
necessari al metodo per svolgere il suo compito debbano essere passati
dal client stesso al momento dell’invocazione.
Questa
impostazione progettuale da luogo ad un’altra importantissima conseguenza:
un bean di questo tipo infatti non ha nessun legame con il client che lo
ha invocato, e durante il processo di swapping dei vari oggetti remoti,
un bean potrà essere associato indipendentemente ad un client piuttosto
che ad un altro.
Alla
luce di queste considerazioni può apparire piuttosto ovvio come
questi componenti risultino essere particolarmente utili in tutti quei
casi dove una determinata attività possa essere svolta da una singola
invocazione di un metodo remoto.
Uno
stateless quindi è spesso considerato un componente che raccoglie
alcuni metodi di servizio.
Quindi,
dove non sia necessario mantenere traccia delle invocazioni da parte
del client (il cosiddetto conversational state), gli stateless offrono
una elevata semplicità operativa ed implementativi, unitamente ad
un buon livello di prestazioni.
Invocazione di
metodi
Il
meccanismo di base con cui sono invocati metodi remoti di un session è
molto simile a quanto visto fino ad ora per gli entity: l’interfaccia remota,
derivando anch’essa dalla EJBObject, eredita le stesse funzionalità
anche se in questo caso di possono trovare alcune importanti differenze.
La più lampante e forse più importante è quella legata
alla diversa modalità con cui il bean può essere ricavato
da remoto. Il metodo getPrimaryKey() infatti genera una RemoteException
dato che un session non possiede una chiave primaria.
Con
la specifica 1.0 questo dettaglio era lasciato in sospeso, ed alcuni server
potevano restituire un null.
Il
metodo getHandle() invece restituisce un oggetto serializzato che rappresenta
l’handle al bean, handle che potrà essere serializzato e riutilizzato
in ogni momento: la condizione stateless infatti consente di riferirsi
genericamente ad un qualsiasi bean del tipo puntato dall’handle, piuttosto
che ad una particolare istanza.
Per
accedere all’oggetto remoto associato al bean si può utilizzare
il metodo getObject() della interfaccia Handle
public
interface javax.ejb.Handle{
public
abstract EJBObject getEJBObject()
throws RemoteException;
}
Comportamento
opposto è quello degli stateful, dove un handle permette di ricavare
l’istanza associata al componente per il solo periodo di validità
del bean all’interno del container.
Se
il client distrugge esplicitamente il bean, tramite il metodo remove(),
o se il componente stesso supera il tempo massimo di vita, l’istanza viene
distrutta, e l’handle diviene inutilizzabile. Una successiva invocazione
al metodo getObject() genera una RemoteException.
In
precedenza si è parlato del meccanismo di wrapping delle eccezioni
sui metodi remoti: tale tecnica continua ad essere utilizzata anche per
i session , ed anzi è utile aggiungere una ulteriore precisazione.
Ciclo di vita
di uno stateless bean
La
tipologia del ciclo di vita di uno stateless bean è fortemente dipendente
dalla natura stessa di questo genere di componenti: dato che non rappresentano
una particolare astrazione dati, ne mantengono lo stato fra due invocazioni
successive del client, il ciclo di vita risulta essere molto semplificato.
Gli
stati che lo compongono sono solamente due, ed il passaggio da uno all’altro
coinvolge un numero molto ridotto di operazioni: il primo, detto not exist
state, corrisponde alla non esistenza di alcun componente all’interno del
container.
Il
secondo stato è invece il cosiddetto ready-pool, e corrisponde allo
stato in cui il bean è pronto per l’invocazione da parte del client.
Figura
1 - Ciclo di vita di un session bean stateless
Questo
stato è piuttosto simile all’instance pool degli entity, e rappresenta
una delle principali differenze con gli stateful bean, i quali non hanno
uno stato di pool.
Un
bean entra nel ready-pool quando il container ne ha bisogno: anche se tale
dettaglio è lasciato indefinito, molti server instanziano un numero
arbitrario di componenti al momento della inizializzazione del container,
prima che un qualsiasi client richieda l’utilizzo; quando il numero dei
bean istanziati risulta essere insufficiente, il server ne istanzia altri.
Analogamente,
quando il numero dei bean allocati è superiore alle reali necessità,
il container rimuove dal ready-pool uno o più bean senza effettuare
una particolare distinzione su quali eliminare, visto che tutti i componenti
sono perfettamente identici.
Il
passaggio verso il ready-pool avviene secondo una procedura ben precisa,
che vede tre distinte fasi: per prima cosa il bean viene istanziato per
mezzo del metodo Class.newIstance() corrispondente ad una specie di istanziazione
statica del componente.
Successivamente
tramite il metodo SessionBean.setSessionContext() al bean viene associato
un contesto (classe EJBContext), che verrà mantenuto per tutto il
suo periodo di vita.
Dato
che uno stateless bean non prevede nel suo ciclo di vita lo stato di passivazione
quando viene salvato su memoria secondaria, il context può
essere memorizzato indifferentemente in una variabile persistente o non
persistente, anche se la direttiva Sun invita i vari costruttori alla prima
soluzione.
Alla
terminazione del metodo ejbCreate() si considera concluso il processo di
creazione del componente. Tale metodo viene invocato una sola volta durante
il ciclo di vita del componente in corrispondenza del passaggio nello stato
di ready-pool.
Quindi
nel momento in cui il client invoca il metodo create() della home interface
non si ha nessuna ripercussione sul componente che esiste già nello
stato di ready-pool: in particolare non si ha l’invocazione del metodo
ejbCreate(), ma semplicemente si passa direttamente alla creazione di una
istanza sul client.
Quando
il bean si trova nello stato ready-pool è pronto per servire
le richieste dei vari client: quando uno di questi invoca un metodo remoto
dell’EJBObject relativo, il container associa alla interfaccia remota un
bean qualsiasi prelevato dal pool per tutto il periodo necessario al metodo
di svolgere il suo compito.
Si
è detto che uno stateless può essere pensato come una raccolta
di metodi di servizio, e quindi al termine della esecuzione il bean viene
disassociato dal bean: questo comportamento è radicalmente differente
sia da quello degli entity bean che dei session stateful, dove il bean
resta associato allo stesso EJBObject per tutto il tempo in cui il client
ha bisogno di interagire con il bean.
Il
motivo di questa scelta è legata alla non necessità di mantenere
alcuna informazione nel bean, e si ripercuote positivamente sulle
prestazioni complessive come sulla quantità di memoria occupata
dal sistema.
Sempre
a causa della mancanza di persistenza dei dati di uno stateless non verranno
mai invocati i metodi in callback ejbActivate() ed ejbPassivate().
In
definitiva il processo di istanziazione di un bean di questo tipo è
molto più semplice rispetto agli altri casi: quello che però
non cambia è la modalità con cui il client ricava un reference
remoto. Ad esempio
Object
obj = jndiConnection.lookup(jndi_bean_name);
MyBeanHome
mbh=( MyBeanHome)
PortableRemoteObject.narrow(obj,
MyBeanHome.class);
MyBean
mb=mbh.create();
Il passaggio
inverso, da ready-pool a not exist, avviene quando il server non necessità
più della istanza del bean: questo avviene normalmente quando il
ready-pool è in sovranumero rispetto alle necessità del sistema.
Il
processo comincia dall’invocazione del metodo ejbRemove(), fase in cui
il bean effettua tutte le operazioni necessarie per terminare in modo corretto
(come ad esempio chiudere una connessione verso una sorgente dati).
L’invocazione
da parte del client del metodo remove() invece non implica nessun cambiamento
di stato da parte del bean, ma semplicemente rimuove il reference dall’EJBObject,
che tra le altre cose comunica al server che il client non necessita più
del reference.
E’
quindi il container che effettua l’invocazione del’ejbRemove(), ma solamente
al termine del suo ciclo di vita. Durante l’esecuzione di tale metodo il
contesto è ancora disponibile al bean, e viene rilasciato solo al
termine di ejbRemove().
Gli stateful beans
Questi
componenti sono una variante del tipo precedente e per certi versi possono
essere considerati una via intermedia fra gli stateless bean e gli entity.
Riconsiderando
il paragone con il meccanismo di invocazione da parte del browser di un
programma CGI server side, si è detto che uno stateless implementa
la funzione di service di una GET o POST senza poter mantenere lo stato
fra una invocazione e la successiva.
In
tale ottica gli stateful possono essere considerati come dei programmi
CGI in cui grazie ad un particolare meccanismo (cookie o altro) sia possibile
memorizzare in qualche modo uno stato.
La
definizione che descrive i session come oggetti lato server funzionanti
come wrapper della business logic del client, è sicuramente più
adatta al caso degli stateful che non degli stateless (per i quali è
forse più corretto il concetto di componenti di servizio).
Questo
significa che un determinato bean servirà per tutta la sua vita
lo stesso client, anche se il server manterrà sempre attivo
un meccanismo di swap virtuale fra le varie istanze dello stesso bean.
Per
gli stateful quindi, essendo ognuno di questi componenti dedicati esclusivamente
a servire uno e sempre lo stesso client, non insorgono problemi di accesso
concorrente.
In
definitiva, alla luce di queste considerazioni prende ancora più
importanza la differenza che sussiste fra un stateless ed un stateful:
il primo è molto vicino ad essere una raccolta di metodi di servizio,
il secondo invece rappresenta l’agente sul server del client.
Ciclo di vita
di un bean stateful
La
principale differenza nel ciclo di vita di uno stateful rispetto agli stateless
risiede nella assenza del meccanismo di pool del bean: quando un componente
viene creato ed assegnato al client, continua a servire lo stesso client
per tutto il suo periodo di vita.
Formalmente
non esiste nessun meccanismo di instance pooling, anche se poi le diverse
implementazioni dei server in commercio utilizzano tecniche di ottimizzazione
delle risorse effettuando lo swapping in vari modi, anche se tale procedimento
viene effettuato in modo del tutto trasparente e non fa parte della specifica
ufficiale.
Il
legame diretto e permanente che si instaura fra il client e l’EJBObject,
in realtà viene virtualizzato durante i lunghi periodi di inattività
il bean quando viene dereferenziato ed eliminato dal garbage collector.
Per
questo motivo è fornito un meccanismo di persistenza (salvataggio
o passivazione e caricamento da memoria o riattivazione) al fine di mantenere
il cosiddetto conversational state fra il client ed il bean.
Il
bean ha la percezione del passaggio da uno stato ad un altro solo se implementa
l’interfaccia SessionSynchronization, che fornisce un set di metodi di
callback per la notifica degli eventi al bean, cosa particolarmente
utile nel momento in cui si abbiano transazioni. Questi aspetti verranno
ripresi in seguito questi aspetti quando si parlerà di transazioni.
Gli
stati del ciclo di vita di uno stateful sono tre: not exist, ready e passivated.
Figura
2 - Gli stati del ciclo di vita di uno stateful sono tre:
not
exist, ready e passivated.
Il
primo è praticamente identico al caso degli stateless e corrisponde
allo stato in cui il bean è rappresentato esclusivamente da un
insieme di file sul file system.
Quando
il client invoca il metodo create() della home interface, il container
comincia la fase di creazione del componente che inzia il suo ciclo di
vita.
Per
prima cosa il server crea un’istanza del bean tramite il metodo newIstance(),
e successivamente assegna un contesto tramite il setSessionContext().
Infine
il container invoca il metodo ejbCreate() al termine del quale il componente,
pronto per essere utilizzato, passa nello stato di ready.
In
questa fase il componente è libero di rispondere alle invocazioni
da parte del client, di accedere a risorse memorizzate nel database, e
di interagire con altri bean.
Il
bean può uscire dallo stato di ready per tornare in not exist, oppure
per fluire nel passivated. Il passaggio a questo tipo di stato avviene
in corrispondenza di una operazione di rimozione esplicita da parte del
client, tramite il metodo remove(), oppure su azione del container: questa
seconda opzioni può avvenire ad esempio se il bean va in time out
(tempo massimo di inattività specificato da parametro al momento
del deploy) cosa che ne provoca la rimozione a meno che non stia eseguendo
una transazione.
Nella
specifica 1.1 il metodo ejbRemove() non viene invocato, come invece accade
nella 1.0.
Il
bean passa allo stato passivated dopo un periodo più o meno lungo
di inutilizzo da parte del client. In questo caso viene rimosso dalla memoria
principale (ready state) e reso persistente tramite un qualche meccanismo
dipendente dal server (ad esempio tramite serializzazione o memorizzazione
nella cache).
In
questo caso tutte le variabili transient o non serializzabili, ovvero tutto
ciò che non dovrà essere reso persistente, dovranno essere
messe a null prima della memorizzazione del bean, al fine di impedirne
una errata invocazione da parte del client.
Tutto
il resto invece verrà serializzato dal server.
Nella
specifica 1.1 alcune variabili, fra cui SessionContext, EJBHome,
EJBObject, UserTransaction e Context, durante il passaggio di stato non
verranno serializzate, ma gestite in modo particolare e direttamente dal
server.
Infine
se il componente va in time out durante lo stato di passivated, verrà
eliminato così come verranno eliminati tutti i riferimenti nella
memoria.
Il
passaggio di stato inverso, da passivated a ready, avviene quando un client
effettua una invocazione di un metodo remoto di un bean reso persistente.
In
questo caso il componente viene deserializzato e reso disponibile: la deserializzazione
segue le regole della serializzazione standard introdotta in Java
con il JDK 1.1, con la sola eccezione relativa all’assegnazione del valore
iniziale delle variabili non persistenti.
Normalmente
in questi casi tutte le variabili primitive numeriche sono inizializzate
a zero, mentre le altre non serializzabili a null. In questo caso la specifica
lascia del tutto in sospeso questo aspetto (lasciato quindi al costruttore
del server), per cui è bene non fare affidamento al valore assegnato
a tali variabili, ma piuttosto utilizzare il metodo ejbActivate() per effettuare
una corretta inizializzazione.
Conclusioni
Termina
questo mese la trattazione teorica del modello EJB: dopo aver parlato a
lungo dei servizi, dei bean di tipo session ed entity passeremo finalmente
il mese prossimo a trattare un esempio completo di una applicazione multistrato
basata su EJB. |