JBoss Seam: Web Application di nuova generazione

IV parte: Contesti e componentidi

Dopo aver creato e analizzato la struttura del progetto attraverso seam-gen passiamo ad analizzare le caratteristiche salienti di Seam. Vedremo come in Seam è possibile trasformare gli oggetti, che costituiscono la struttura della nostra applicazione, in componenti e affidarli a Seam, oltre a gestire il loro ciclo di vita. Tutto questo fornirà diversi utili servizi.

Introduzione

In Seam acquista fondamentale importanza il concetto di componente e Seam stesso può essere pensato come un container di componenti. Una caratteristica interessante di Seam è che non è necessario adottare modelli di programmazione speciali, o codificare interfacce particolari legate al container. Inoltre, a rendere Seam unico è il fatto che esso riesce a coesistere con altri container e contesti  che già ospitano oggetti. Non è neanche obbligatorio far diventare ogni oggetto un componente Seam se non si vuole usufruire dei servizi offerti dal container. Oltre al concetto di componente, ha un ruolo fondamentale il concetto di contesto, infatti ogni componente viene legato a un contesto che ne determinerà la visibilità e la durata.

Contesti

Possiamo pensare a Seam come a un contenitore di nomi di variabili diviso in diversi compartimenti. Ogni compartimento rappresenta uno scope, o per usare la terminologia di Seam, un context che definisce il luogo dove trovare alcuni nomi di variabili e il tempo di permanenza di questi nomi nel contesto. Questi nomi di variabili, ossia queste variabili di contesto, possono far riferimento a qualsiasi oggetto.

Sicuramente il concetto di scope non è nuovo per chi lavora con la Java EE e infatti saranno noti i contesti Application, Session e Request. Seam ne aggiunge altri e in particolare abbiamo pure Stateless, Page, Conversation e Business Process.

Nelle applicazioni web tradizionali, lo stato di un'interazione lunga, costituita da diverse richieste e risposte, viene memorizzata nella sessione HTTP, che rappresenta il contesto stateful de facto. Seam permette e incoraggia l'uso di contesti con un lifetime che meglio si adatta alla durata di un'interazione lunga. In particolare, i due nuovi context Conversation e Business Process sono adatti a questo scopo e permettono di controllare la durata dell'interazione mediante l'uso di annotazioni o di tags all'interno delle pagine. Il Business Process è una variante del Conversation dove è possibile passare lo stato tra multipli utenti dell'applicazione. Questi context impediscono l'uso improprio della sessione HTTP.

I contesti base di Seam sono:

  • Stateless context
  • Event (o request) context
  • Page context
  • Conversation context
  • Session context
  • Business process context
  • Application context

Componenti

In teoria, un componente è un modulo che può essere usato in un'applicazione allo stesso modo in cui viene usato un mattoncino Lego per creare una qualche costruzione. Ovviamente siamo tutti convinti del fatto che un componente Seam è qualcosa di un pò più complesso di un mattoncino Lego. Un componente avrà un nome, che verrà usato per individuare il componente stesso all'interno del container. Quando una classe diventa un componente, viene completata con un set di istruzioni in maniera tale da  usufruire di alcuni servizi. Ad esempio, i metodi di un session bean EJB vengono racchiusi automaticamente in una transazione; le servlet e i managed bean JSF possono usare le injections; i bean Spring possono essere injected tramite altri bean. Quindi essere un componente è vantaggioso perchè garantisce dei privilegi. Vediamo allora come è costituito un componente Seam. Esso contiene:

  • metadata relativi alla creazione di un'istanza;
  • metodi per il ciclo di vita;
  • valori iniziali per le proprietà.

Seam crea le istanze dei componenti dalla definizione del componente. Quando l'applicazione interagisce con un componente, in realtà essa sta lavorando con una istanza di quel componente e non con la definizione del componente.

 

 

Figura 1 - Le istanze del componente sono create dal container Seam.

 

La relazione tra un componente e la sua istanza è la stessa che esiste tra una classe Java e un oggetto Java. Una volta che una istanza di un componente è creata, essa verrà memorizzata come un attributo del contesto designato sotto il nome del componente, diventando una variabile di contesto. Una istanza di un componente è solo una oggetto Java, ma con una sola eccezione: Seam tiene traccia dell'oggetto e gestisce il suo ciclo di vita. La modalità di gestione del componente viene decisa tramite le annotazioni e quindi è possibile definire le injection, le transazioni, i vincoli sulla sicurezza, la gestione degli eventi sollevati dal componente.
Questo ricorda il funzionamento degli EJB e è proprio a questi che Seam si è ispirata. Ad esempio è il container a fornire un'istanza del componente quando l'applicazione ne fa richiesta. Quando arriva la richiesta relativa al nome associato a un componente Seam, prima verifica l'esistenza del componente e se non lo trova lo crea, poi consegna l'istanza al richiedente. Quindi con Seam non occorre più creare le istanze delle classi esplicitamente attraverso l'operatore new, sebbene sia sempre possibile farlo, ma sarà Seam a farlo per noi. Da questo punto di vista, il container Seam può essere considerato come una fabbrica di istanze di componenti, che usa le definizioni dei componenti come schema per la creazione delle istanze. Oltre ad occuparsi della creazione delle istanze, Seam fornisce ai componenti delle funzionalità equivalenti a quelle fornite da un container EJB, come ad esempio la gestione container-managed delle transazioni e della sicurezza. In Seam, le istanze hanno una durata che è legata al contesto ove risiedono.

Definire un componente tramite le Annotazioni

Per definire un componente è possibile usare sia le annotazioni sia XML, ma dal momento che l'obiettivo di Seam è quello di ridurre al massimo l'uso di file di configurazione in XML, il metodo preferito è quello tramite le annotazioni. Riportiamo di seguito un riepilogo con le principali annotazioni usate per la definizione dei componenti: dopo il nome dell'annotazione, riportiamo la sua funzione.

@Name

Dichiara la classe come componente Seam e assegna ad esso il nome indicato. Per richiedere questo componente occorrerà usare il nome indicato. Seam si preoccuperà di istanziare la classe e legarla al nome creando una variabile di contesto.

@Scope

Specifica lo scope di default in cui memorizzare la variabile di contesto quando un'istanza del componente viene creata.

@Role (@Roles)

Permette di creare coppie di nomi-scope alternativi che possono essere usati per istanziare diverse istanze del componente parallelamente. Dichiarazioni multiple di ruoli (@Role) devono essere racchiuse in un'annotazione @Roles.

@Startup

Indica  a Seam di istanziare il componente automaticamente quando viene avviato il contesto assegnato (si applica ai componenti con contesto Application e Session).

@Namespace

Lega un URI a un package Java e viene usato per definire dei namespace XML nel descrittore del componente Seam

@Install

Permette di creare delle condizioni per la creazione di un componente. La condizione potrebbe essere la presenza di una classe nel classpath, la presenza di un altro componente, o l'uso della modalità di debug.

@Autocreate

Indica a Seam di creare una nuova istanza del componente quando il nome del componente  viene richiesto per la prima volta.

 

Le annotazioni principali sono @Name e @Scope e già da sole permettono di completare la definizione di un componente. Le altre annotazioni sono ausiliari.

Creare un componente con @Name e @Scope

La modalità principale per creare un componente avviene tramite l'aggiunta dell'annotazione @Name alla dichiarazione della classe. Poichè occorre fornire un nome ad ogni componente è necessario usare l' attributo value, che diventerà il nome della variabile di contesto a cui l'istanza sarà legata. È possibile usare l'annotazione @Name su qualsiasi classe che si vuole far diventare un componente Seam. La lista di candidati a diventare componenti Seam include:

  • POJO (JavaBean, classi Groovy, bean Spring)
  • EJB (Stateless session bean, Stateful session bean, Message-driven bean)
  • Classi JPA

L'annotazione @Scope permette di definire il context e lo scope in cui l'istanza del componente sarà memorizzata dopo essere stata istanziata dal container Seam. Ogni tipologia di componente ha uno scope predefinito, come riportato nello schema seguente:
 

Tipologia componente

Default scope

EJB Stateful Session Bean Conversation
EJB Stateless Session Bean Conversation
PA Entity Class
Stateless
EJB Message Driven Bean Stateless
JavaBean (POJO) Event

L'uso dell'annotazione @Scope permette di impostare uno scope diverso. Ad esempio, per far diventare componente la classe Clienti basta aggiungere queste due annotazioni a quelle già presenti

import java.util.HashSet;
...
import javax.validation.constraints.Size;
 
 
@Entity
@Table(name = "clienti", catalog = "shopdb")
@Name("nuovoCliente")
@Scope(ScopeType.EVENT)
public class Clienti implements java.io.Serializable {
       private int idCliente;
       private String nomeAzienda;
       private String nome;
       private String titolo;
 
       public Clienti() {...}
       public Clienti(int idCliente) {...}
       ...
}

Definire le condizioni per la creazione di un componente con @Install

Quando Seam trova una classe annotata con @Name, il comportamento di default è quello di creare un componente, tranne se la classe è annotata con @Install. Infatti tramite quest'ultima annotazione è possibile definire delle condizioni per la creazione del componente, e questo può essere usato per gestire la selezione di una implementazione di un componente tra le implementazioni alternative presenti. Ci sono infatti casi in cui si ha la necessità di effettuare logiche differenti, ad esempio per supportare  differenti implementazioni di API, o application server. Per evitare di mettere nello stesso componente tutto il codice necessario ai diversi casi, si può scegliere di separare la logica in diverse implementazioni di un componente e lasciare delle indicazioni a Seam per selezionare l'implementazione adatta. Alle diverse implementazioni assegniamo lo stesso nome, ma mettiamo l'annotazione @Install per indicare sotto quali condizioni utilizzare quella particolare implementazione, ad esempio la presenza o meno di una particolare classe di un'implementazione delle JSF. Possiamo ad esempio creare un componete per l'implementazione Sun delle JSF

@Name("jsfAdapter")
@Install(classDependencies = "com.sun.faces.context.FacesContextImpl")
public class SunJsfAdapter implements JsfAdapter {...}

e creare un altro component per l'implementazione MyFaces

@Name("jsfAdapter")
@Install(classDependencies =  "org.apache.myfaces.context.servlet.ServletFacesContextImpl")
public class MyFacesJsfAdapter implements JsfAdapter {...}

Un altro caso in cui l'annotazione @Install è molto utile è quella che permette di usare componenti diversi a seconda che si operi in debug mode o meno. Basta aggiungere l'attributo debug=true all'annotazione @Install e Seam caricherà quel componente solo se si sta operando in Debug Mode di Seam. Questo viene fatto attraverso la proprietà debug del componente standard Seam org.jboss.seam.core.init che dovrà essere settata a true nel relativo descrittore di componente:


Se è true Seam attiva i componenti con @Install (debug=true). Quando l'applicazione viene deployata in un ambiente di produzione, la proprietà debug viene posta a false e quindi verranno caricati i componenti non-debug con lo stesso nome.

È anche possibile definire una precedenza tra componenti con lo stesso nome che tentano di occupare lo stesso spazio. La precedenza viene impostata tramite un numero intero assegnato come attributo della @Install. Più è alto tale numero maggiore sarà il punteggio assegnato al componente. Tutti i componenti di Seam hanno una precedenza 0 (Install.BUILT_IN) in maniera tale da essere facilmente scavalcati, mentre i componenti definiti dall'utente hanno di default la precedenza massima di 20 (Install.APPLICATION).

Assegnare ruoli multipli con @Roles

Combinazioni alternative di nomi e scope possono essere assegnate usando l'annotazione @Role. L'idea che sta dietro al concetto di ruolo è quella di poter far istanziare e gestire da Seam lo stesso componente più volte per usarlo per scopi diversi. È possibile usare anche diverse istanze dello stesso componente nello stesso scope simultaneamente.

Istanziare un componente allo Startup

Dopo la scansione di tutti i componenti, nel container Seam avremo un insieme di definizioni di componenti, ma nessuna istanza ancora. Normalmente, la definizione di un componente verrà usata per creare l'istanza nel momento in cui il componente viene richiesto per la prima volta. C'è anche la possibilità di creare l'istanza del componente anche se non è stato ancora richiesto. Questo viene fatto tramite l'annotazione @Startup, che istruisce Seam a prendere l'iniziativa di creare l'istanza del componente nel momento in cui lo scope del componente viene inizializzato. Attualmente solo i componenti con scope Application e Session possono sfruttare questa caratterisitca. Quindi ad esempio, se il componente ha scope Application e ad esso viene aggiunta l'annotazione @Startup, quel componente verrà istanziato all'avvio dell'applicazione. Questa modalità è utile con tutti i componenti che usano il design pattern Singleton

Se invece il componente ha scope Session e è annotato con @Startup, allora l'istanza verrà creata quando viene avviata la sessione HTTP. Questo permette di avere componenti per singoli utenti istanziati automaticamente. Per stabilire l'ordine di creazione delle istanze è possibile utilizzare l'attributo depends per indicare la lista di componenti da istanziare prima.

Component Life

Prima che il container Seam possa usare la definizione di un componente per creare un'istanza è necessario che tale definizione sia rintracciata dal container e che inoltre siano soddisfatti i prerequisiti. Solo dopo il container Seam può creare un'istanza del componente, e questo lavoro può essere fatto all'avvio dell'applicazione, se il componente è stato annotato in tal senso, oppure quando viene richiesto. Durante il processo di inizializzazione, uno scanner scorre i classpath cercando quelle classi con l'annotazione @Name. Inoltre vengono anche controllati gli opportuni file XML per verificare se ci sono definizioni di componenti effettuate tramite file di configurazione. Per ogni classe dichiarata come componente, Seam crea un componente definizione e lo memorizza nell'Application Scope. Il nome dell'attributo sotto cui tale componente viene memorizzato è derivato dal nome del componente seguito da .component. Ad esempio, se una classe è stata annotata con @Name ("registerAction"), la definizione avrà registerAction.component. I classpath scansionati sono solo quelli che contengono nella propria root un file con nome seam.properties, o quelle che nella cartella META-INF contengono un descrittore di componente, ovvero il file components.xml. Utilizzando queste tecniche, Seam sa perfettamente dove andare a cercare. Indipendentemente da quando l'istanza viene creata, esistono dei metodi di callback relativi al suo ciclo di vita che vengono invocati prima che l'istanza sia consegnata al richiedente. Anche quando il componente va fuori scope o viene distrutto ha ancora una chance per effettuare del lavoro.

Life-cycle callback

Abbiamo precedentemente detto che è possibile partecipare al ciclo di vita di un componente grazie alle funzioni di callback. È possibile infatti registrare due metodi speciali da invocare quando l'istanza viene creata e quando viene distrutta. Questi metodi vengono identificati tramite l'uso di annotazioni per cui il nome del metodo è irrilevante. L'unico vincolo è che ci dovrà essere solamente un metodo per la creazione e uno solo per la distruzione per componente. Il metodo del componente annotato con @PostConstruct viene chiamato subito dopo che l'istanza è stata creata e inizializzata, mentre il metodo annotato con @PostDestroy viene chiamato prima che il componente sia rimosso dal contesto di appartenenza. Il metodo taggato con @PostConstruct è utile per effettuare tutte quelle operazioni di post-inizializzazione, come ad esempio la validazione o il loading di risorse ausiliari necessari al componente. Se il componente è di tipo startup, il metodo @PostConstruct può essere usato per effettuare tutte le operazioni necessari all'avvio dell'applicazione o della sessione utente. Ad esempio è possibile avviare database in-memory e inserire i dati iniziali, lanciare un servizio di indicizzazione, o far partire un servizio, libreria, container di terze parti (ad esempio Spring, JMS, jBPM).

Consideriamo un semplice esempio in cui useremo i metodi di callback per scrivere un log ogni volta che un componente viene creato o distrutto:

@Name("registerAction")
public class RegisterAction {
    ...
    @PostConstruct                     
    public void onCreate(Component self) {
        log.debug("Created " + self.getName() + " component");
    }
    @PreDestroy
    public void onDestroy() {                       
        log.debug("Destroyed registerAction component");
    }
}

I componenti JavaBean supportano anche le annotazioni standard Java EE @PrePassivate e @PostActivate. I metodi taggati con queste annotazioni vengono invocati da Seam quando la sessione HTTP è trasferita tra nodi di un cluster.

Creare dei collegamenti tra componenti

Il metodo principale per legare dei componenti avviene tramite un meccanismo chiamato bijection. Anche qui si fa uso delle annotazioni, infatti verrà usata l'annotazione @In su una proprietà JavaBean di un componente per indicare a Seam di assegnare a tale proprietà il valore di una istanza di componente il cui nome corrisponde al nome della proprietà. Questo meccanismo, in genere  chiamato injection, è bidirezionale per cui si usa il prefisso bi. L'annotazione @Out posizionata su una proprietà JavaBean di un componente indica a Seam che il valore di quella proprietà deve essere legata e inserita ad una variabile di contesto con lo stesso nome.

EJB3 in Seam

Con Seam è possibile far diventare componenti anche gli EJB3. In questo caso la maggior parte del lavoro di gestione del componente verrà fatto dal container EJB 3. Seam si preoccuperà solamente di registrare il componente come variabile di contesto e intercettare le varie chiamate di metodi sul componente. I session bean che diventano dei componenti Seam acquistano una doppia personalità, dal momento che agiranno sia come componenti Seam che come componenti EJB 3, e sfrutteranno i servizi forniti da entrambi i container. Sarà infatti il container EJB a gestire il session bean, ma Seam metterà mano al ciclo di vita dell'EJB intercettando le chiamate dei metodi e fornendo i propri servizi. Una differenza importante con gli altri componenti Seam, è che Seam non creerà le istanze dei session bean in quanto sarà compito del container EJB. Quando il container Seam determina che è necessaria una istanza di un componente EJB, chiederà al container EJB un riferimento ad una istanza di session bean tramite una lookup JNDI. Ottenuto il riferimento, Seam lega l'istanza alla variabile di contesto e fornirà i servizi che fornisce normalmente agli altri componenti.

Per i session bean di tipo stateful, Seam richiede solamente la definizione di un metodo senza argomenti da taggare con l'annotazione EJB3 @Remove. Questo metodo sarà usato da Seam per indicare al container EJB di distruggere il session bean quando la variabile di contesto seam deve essere eliminata.

import ...;
import javax.ejb.Stateful;
@Stateful
@Name("registerAction")
@Scope(ScopeType.EVENT)
public class RegisterActionBean implements RegisterAction {
    ... 
    @Remove public void destroy() {}
}

I metodi del componente EJB verranno automaticamente inseriti all'interno di una transazione. Se viene disabilitata la gestione delle transazione di Seam, agli EJB3 verranno applicate le regole di gestione delle transazione di EJB3.

Conclusioni

In questo articolo abbiamo trattato due concetti importanti in Seam: i componenti e i contesti. Ogni componente viene legato a un particolare contesto e Seam tiene traccia di tutti i componenti dell'applicazione gestendone il ciclo di vita. Abbiamo anche visto l'uso della annotazioni per definire i componenti e le caratteristiche degli stessi. Con Seam è possibile far diventare componente quasi tutti gli oggetti Java e grazie al container possiamo ottenere dei servizi proprio come succede con gli EJB e i relativi container. Abbiamo anche visto che Seam coesiste con altri container e riesce a cooperare con essi per fornire un interfaccia comune con tutti gli oggetti che diventano dei componenti. Oltre ad istanziare i vari componenti, Seam fornisce delle funzioni di callback e ne gestisce il ciclo di vita.

Riferimenti

[1] Sito di riferimento per il Framework Seam

http://seamframework.org

 

[2] Dan Allen, "Seam in Action", Manning Publications, 2009

 

 

Condividi

Pubblicato nel numero
180 gennaio 2013
Nato a Vittoria (RG) ha conseguito la laurea in Ingegneria Informatica all’Università di Catania nel luglio del 2003. Si interessa del mondo Java da più di 10 anni e dopo aver lavorato per 3 anni come sviluppatore software su piattaforma J2EE, oggi svolge attività di consulenza e formazione in provincia…
Articoli nella stessa serie
Ti potrebbe interessare anche