Il mese precedente è iniziata questa serie dedicata alla nuova specifica EJB 3.0. Si è parlato del nuovo approccio e della nuova filosofia adottata da Sun in tale contesto. Si sono introdotti i session beans e la modalità di invocazione del client. Questo mese proseguiamo la trattazione presentando le nuove modalità di interazione con il ciclo di vita dei beans, e alla nuova modalità di accesso al contesto JNDI.
Interagire con il ciclo di vita: modalità base e avanzata
Il ciclo di vita di un bean all‘interno della specifica EJB 2.0 è strettamente dipendente dal fatto che un componente ha interfaccia remota ed è legato all‘ejbObject creato dal container, tanto che viene associato, passivizzato, creato, distrutto in funzione di questo dualismo: i metodi di callback in EJB 2.0 forniscono i punti di entrata al programmatore per inserire logica di controllo da eseguire al verificarsi di un cambio di contesto.
Tali metodi sono molto importanti perché offrono al programmatore il modo per “entrare” nel ciclo di vita, eseguendo logica di controllo di vario tipo (dalla gestione delle risorse esterne alla chiusura pulita di connessioni o relazioni con componenti esterni); da un punto di vista prettamente pratico il programmatore è obbligato a seguire lo schema rappresentato dai metodi di callback, accettando tale filtro come l‘unico modo per conoscere cosa accade dentro la “scatola nera”. Effettivamente è bene considerare che normalmente nessuno è interessato a quando il container invocherà il metodo ejbPassivate(), ma al momento in cui il bean sia messo a risposo. Nel caso dei session bean i metodi di callback indicano un particolare cambio di stato, mentre nel caso di un entity bean tali metodi segnalano la nascita o la cancellazione di un dato nel datamodel, e quindi sono importanti ad esempio per gestire le relazioni fra bean in maniera corretta.
Con l‘avvento della nuova release non sono cambiati i concetti base legati al ciclo di vita di un bean, così come continua a essere impossibile interferire con la politica di gestione messa in atto dal container.
Come si è potuto vedere in precedenza, la nuova release elimina la necessità di legare il bean a una interfaccia prefissata (le EJBHome e EJBRemote), utilizzando il meccanismo delle annotazioni per infondere semantica enterprise a un semplice POJO.
Questo approccio ha un grosso riflesso anche per quello che concerne la gestione del ciclo di vita di un componente: non è più il programmatore (ovvero il bean una volta deployato) a dover monitorare o tenere sotto controllo il ciclo di vita del bean implementando i metodi relativi. Si rovescia infatti il punto di vista del processo, e quindi di fatto il bean POJO viene “visto” dal container e tenuto sotto controllo, senza che il componente ne abbia la percezione.
Il programmatore adesso ha la possibilità di definire un proprio set di metodi che possono essere successivamente associati a predeterminati momenti del ciclo di vita, tramite l‘utilizzo di apposite annotazioni. Non si tratta di metodi di callback predefiniti nella specifica o ereditati da qualche classe astratta o interfaccia, ma di semplici metodi di logica.
Ad esempio si immagini di avere i seguenti metodi nel POJO all‘interno dei quali si voglia inserire logica di controllo:
public void initializeComponent () {...}
public void shutdownDependentSystem(){...}
I metodi non devono seguire alcune regola di naming, anche se al solito è opportuno scegliere nomi che siano facilmente riconducibili al significato e quindi in quale fase momento sia necessario eseguirli. Tramite l‘utilizzo di apposite annotazioni è possibile associare l‘esecuzione del metodo all‘evento desiderato:
@PostConstruct
public void initializeComponent () {
}
@PreDestroy
public void shutdownDependentSystem(){}
Le annotazioni sono predefinite all‘interno della specifica e identificano i principali momenti del ciclo di vita; di seguito è riportato l‘elenco completo delle annotazioni con relativo significato:
- Annotazione @PostConstruct: in questo caso il container esegue il metodo annotato con questo tag immediatamente dopo che il bean è istanziato.; il tag è applicabile sia a componenti di tipo stateless che stateful.
- Annotazione @PreDestroy: il metodo annotato prima viene invocato che il container distrugga e rimuova dal pool un oggetto inutilizzato o scaduto. Applicabile a stateless e stateful.
- Annotazione @PrePassivate: l‘invocazione viene eseguita prima della passivazione per inutilizzo o per la necessità di spazio nel pool. In questo caso il container salva lo stub serializzandolo e rende nuovamente disponibile il bean nel pool (o lo cancella a discrezione del container). Applicabile a stateless e stateful.
- Annotazione @PostActivate: il metodo annotato viene eseguito alla riattivazione di un oggetto precedentemente passivizzato. In questo caso il container crea un nuovo oggetto e lo associa allo stub; il metodo viene invocato non appena l‘oggetto è nuovamente disponibile. Applicabile a stateful (stateless non hanno stato da ripristinare)
- Annotazione @Init: serve per definire un metodo di inizializzazione anche se questa non necessariamente dee corrispondere alla creazione (costruttore); è possibile definire più metodi di inizializzazione (procedura di inizializzazione), anche se fra tutte le istanze di stateful, in funzione della logica di controllo, solo uno verrà invocato. Il metodo viene invocato prima di @PostConstruct.
Come si può facilmente intuire non vi sono limitazioni di alcuna sorta, e si possono definire più metodi associati ad un evento. Concettualmente l‘utilizzo dei tag sugli step del ciclo di vita non introduce nessun aspetto innovativo. Le nozioni di base valide per la specifica precedente continuano a essere corrette anche in questo caso.
Il JNDI context e Dependency Injection
Uno dei pilastri su cui si basa tutta la piattaforma JavaEE, e quindi anche EJB, è la possibilità di utilizzare un sistema di associazione delle risorse in base ai nomi, in modo da disaccoppiare i vari strati applicativi e i moduli o componenti che costituiscono una applicazione enterprise.
Ad esempio un client può invocare un session bean remoto dopo averne ottenuto il riferimento tramite una ricerca basata su nome logico; analogamente anche il reference di una connessione a un database o una coda di messaggi sono sempre individuate tramite nomi logici.
Questo modo di gestire le risorse tramite nomi logici (con i relativi mapping in codice XML e lookup Java), per quanto concettualmente semplice, ha da sempre generato nei programmi enterprise non pochi problemi e fastidi: si tratta di un punto fondamentale per la buona riuscita dell‘applicazione e spesso introduce legami stretti con le varie logiche di naming degli application server o semplicemente del tree JNDI utilizzato.
Oltre a tutto ciò si deve pur dire che una operazione di lookup e il conseguente cast, sono operazioni ripetitive, per cui poco interessanti, ma che richiedono molta attenzione e controllo per evitare l‘insorgere di problemi a causa di una banale svista.
Alla luce di queste osservazioni dovrebbe essere chiaro come la gestione possa rappresentare un compito semplice ma denso di pericoli e quindi sempre poco amato dai programmatori JavaEE. Sun, spinta dalla necessità di semplificare e amalgamare la nuova release, ha apportato importanti modifiche anche in questo settore: EJB 3.0 introduce una piccola rivoluzione in questo senso.
Lato server le operazioni di mapping risorsa-nome sono automatiche e se non sono necessarie esigenze particolari, sono trasparenti agli occhi del programmatore del bean. Scompare del tutto (tranne in alcuni casi molto particolari) l‘uso dei file XML che lasciano il posto alle annotations.
Il nuovo modello si basa su un pattern piuttosto in voga in questo periodo, ovvero il Dependency Injection che rovescia le responsabilità dei vari soggetti coinvolti. Al solito l‘approccio è POJO oriented, anche se in questo caso non ha senso parlare di risorse mappate sul JNDI tree in un ambito in cui non sia presente un container.
Il meccanismo delle annotazioni permette di definire in un session bean EJB 3.0 un‘associazione con una risorsa esterna, annotando una variabile oppure annotando il corrispondente metodo di set. Il container “inietta” un reference alla risorsa prima che sia eseguito un qualsiasi metodo di business nel bean istanziato. Ogni “iniezione” corrisponde a un‘operazione di lookup JNDI che il container esegue al posto del bean, rendendo disponibile l‘oggetto opportunamente convertito nella classe corrispondente. L‘iniezione sui campi del bean (field injection) può essere eseguita assegnando una annotazione ad un campo come nel seguente esempio:
public class MyStatefulBean implements RemoteStatefulInterface{
@Resource javax.sql.DataSource myDatasource;
}
L‘annotazione @Resource dichiara la dipendenza e in questo caso non specifica nessun parametro. Opzionalmente si possono specificare i parametri “name” e “type”: quando il tipo della risorsa può essere ricavato dal tipo della classe, come nel caso sopra citato, non è necessario utilizzare il type.
Il parametro name invece serve per specificare il nome della risorsa qualora non sia possibile ricavarla oppure nel caso si voglia definirne uno differente da quello che verrebbe composto automaticamente: la specifica JavaEE 5 infatti dice che, per default, il nome di una risorsa viene composto dal fully qualified name della classe combinato con il nome della istanza (e il tutto viene appeso al JNDI naming point “java:comp/env”).
Quindi nel caso precedente il nome automatico della datasource sarebbe
java:comp/env/com.mokabyte.ejb30.sessions/ myDatasource
dove
com.mokabyte.ejb30.sessions
è il nome del package che contiene il session bean MyStatefulBean, mentre
myDatasource
è il nome prescelto per la datasource.
Nel caso in cui sia necessario forzare all‘uso di un nome differente, si può fare ricorso al parametro name che può essere utilizzato nel seguente modo:
@Resource(name="jdbc/myDatasource ",type=javax.sql.DataSource.class)
public javax.sql.DataSource myDatasource;
Una tecnica alternativa è quella che permette di definire una variabile di contesto tramite l‘associazione al metodo di set, secondo la consueta convenzione delle proprietà di un JavaBean. Ecco un esempio:
// definisce la variabile myDatasource
private DataSource myDatasource;
// usa il metodo accessore set per definire la
// associazione con la risorsa del JNDI tree
@Resource
private void setMyDatasource(DataSource ds){
myDatasource=ds;
}
Per quello che concerne nome e tipo, anche in questo caso valgono le stesse considerazioni fatte per il caso precedente. Ecco la versione in cui si specifica un nome differente da quello automatico:
@Resource(name="jdbc/MySecondDatasource")
public void setDataSource(DataSource myDB) {
this.ds = myDB;
}
private DataSource myDB;
Oltre alla possibilità di definire reference verso risorse esterne un utilizzo interessante è quello che permette di definire riferimenti verso altri EJB; l‘annotazione da utilizzare è la @EJB, che permette di “iniettare” un entity o session sia tramite l‘interfaccia remota che locale.
Ecco un esempio
@EJB MySessionBeanLocalInterface mySessionBeanLocalInterface;
Anche in questo caso vale la regola relativa al nome automatico che segue questo schema:
java:comp/env//mySessionBeanLocalInterface
Si faccia attenzione che nel caso in cui il bean riferito sia aderente alla versione 2.1, si dovrà necessariamente far riferimento alla home interface:
@EJB(name="ejb/stateless") RemoteStatelessHome myRemoteHome;
Definizione del comportamento transazionale dei metodi
Parlando di servizi offerti da un container EJB e di come un bean sia gestito all‘interno dell‘application server, una delle cose di cui non si può non fare menzione è la modalità di configurazione delle transazioni sui metodi.
Come per la gestione del JNDI tree, anche per quanto concerne la gestione delle transazioni la specifica non introduce nessun concetto nuovo, ma cambia radicalmente il modo con il quale sono definiti gli scope transazionali dei singoli metodi: da questo punto di vista sono state seguite le regole base della nuova specifica, ovvero semplicità , omogeneità e mascheramento dei dettagli non necessari.
Questo al solito vuol dire annotazioni al posto di lunghi script XML, che per quello che concerne la precedente specifica in materia di transazioni non erano affatto piacevole da gestire.
In EJB 3.0 per definire il comportamento transazionale di un metodo si utilizza l‘annotazione @TransactionAttribute, specificando il tipo di comportamento richiesto; ad esempio si può scrivere
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public String login(String username, String password) {
...
}
I valori concessi sono quelli già presenti concettualmente nella precedente versione: Required, RequiresNew, Supports, Mandatory, NotSupported, Never, i cui significati dovrebbero essere piuttosto chiari (si veda a tal proposito [EJBTX]).
Conclusioni
Si conclude qui questa seconda parte della serie dedicata alla specifica EJB 3.0. Questo mese si sono visti alcuni aspetti che forse non saranno di uso corrente per la totalità dei programmatori, ma che rappresentano uno dei punti focali della nuova specifica.
Per chi avesse necessità di ripassare la teoria di base sugli EJB, consiglio la lettura del capitolo dedicato all‘argomento del libro “Manuale Pratico di Java” consultabile anche in formato elettronico presso [MPJ].
Riferimenti
[EJB] Home page Sun della specifica, http://java.sun.com/products/ejb/docs.html
[MPJ] AA VV, “Manuale Pratico di Java. La programmazione della piattaforma J2EE”, Tecniche Nuove, https://www.mokabyte.it/cms/article.run?articleId=94C-DX8-PQM-L3Y_7f000001_2810448_5df55646
[EJBTX] Giovanni Puliti, “Corso di EJB. Parte VII: la gestione delle transazioni”, MokaByte 54, lug/ago 2001, https://www.mokabyte.it/2001/07/ejb7.htm