SeamFramework.orgCommunity Documentation
I due concetti di base in Seam sono la nozione di contesto e la nozione di componente. I componenti sono oggetti stateful, solitamente EJB, e un'istanza di un componente viene associata al contesto, con un nome in tale contesto. La bijection fornisce un meccanismo per dare un alias ai nomi dei componenti interni (variabili d'istanza) associati ai nomi dei contesti, consentendo agli alberi dei componenti di essere dinamicamente assemblati e riassemblati da Seam.
Segue ora la descrizione dei contesti predefiniti in Seam.
I contesti di Seam vengono creati e distrutti dal framework. L'applicazione non controlla la demarcazione dei contesti tramite esplicite chiamate dell'API Java. I contesti sono solitamente impliciti. In alcuni casi, comunque, i contesti sono demarcati tramite annotazioni.
I contesti base di Seam sono:
contesto Stateless
contesto Evento (cioè, richiesta)
contesto Pagina
contesto Conversazione
contesto Sessione
contesto Processo di Business
contesto Applicazione
Si riconosceranno alcuni di questi contesti dai servlet e dalle relative specifiche. Comunque due di questi potrebbero risultare nuovi: conversation context, e business process context. La gestione dello stato nelle applicazioni web è così fragile e propenso all'errore che i tre contesti predefiniti (richiesta, sessione ed applicazione) non sono significativi dal punto di vista della logica di business. Una sessione utente di login, per esempio, è praticamente un costrutto arbitrario in termini di workflow dell'applicazione. Quindi la maggior parte dei componenti Seam hanno scope nei contesti di conversazione e business process, poiché sono i contesti più significativi in termini di applicazione.
Ora si analizza ciascun contesto.
I componenti che sono stateless (in primo luogo bean di sessione stateless) vivono sempre nel contesto stateless (che è sostanzialmente l'assenza di un contesto poiché l'istanza che Seam risolve non è memorizzata). I componenti stateless non sono molto interessanti e sono non molto object-oriented. Tuttavia, essi vengono sviluppati e usati e sono quindi una parte importante di un'applicazione Seam.
Il contesto evento è il contesto stateful "più ristretto" ed è una generalizzazione della nozione del contesto di richiesta web per coprire gli altri tipi di eventi. Tuttavia, il contesto evento associato al ciclo di vita di una richiesta JSF è l'esempio più importante di un contesto evento ed è quello con cui si lavorerà più spesso. I componenti associati al contesto evento vengono distrutti alla fine della richiesta, ma il loro stato è disponibile e ben-definito per almeno il ciclo di vita della richiesta.
Quando si invoca un componente Seam via RMI, o Seam Remoting, il contesto evento viene creato e distrutto solo per l'invocazione.
Il contesto pagina consente di associare lo stato con una particolare istanza di una pagina renderizzata. Si può inizializzare lo stato nell'event listener, o durante il rendering della pagina, e poi avere ad esso accesso da qualsiasi evento che ha origine dalla pagina. Questo è utile per funzionalità quali le liste cliccabili, dove dietro alla lista sono associati dati che cambiamo lato server. Lo stato è in verità serializzato al client, e quindi questo costrutto è estremamente robusto rispetto alle operazioni multi-finestra e al pulsante indietro.
Il contesto conversazione è un concetto fondamentale in Seam. Una conversazione è una unità di lavoro dal punto di vista dell'utente. Può dar vita a diverse interazioni con l'utente, diverse richieste, e diverse transazioni di database. Ma per l'utente, una conversazione risolve un singolo problema. Per esempio, "Prenota hotel", "Approva contratto", "Crea ordine" sono tutte conversazioni. Si può pensare alla conversazione come all'implementazione di un singolo "caso d'uso" o "user story", ma la relazione non è esattamente uguale.
Una conversazione mantiene lo stato associato a "cosa l'utente sta facendo adesso, in questa finestra". Un singolo utente potrebbe avere più conversazioni in corso in ogni momento, solitamente in più finestre. Il contesto conversazione assicura che lo stato delle diverse conversazioni non collida e non causi problemi.
Potrebbe volerci un pò di tempo prima di abituarsi a pensare applicazioni in termini di conversazione, ma una volta abituati, pensiamo che ci si appassionerà e non si riuscirà più a non pensare in altri termini!
Alcune conversazioni durano solo una singola richiesta. Le conversazioni che si prolungano attraverso più richieste devono essere marcate usando le annotazioni previste da Seam.
Alcune conversazioni sono anche task. Un task è una conversazione che è significativa in termini di processo di business long-running, ed ha il potenziale per lanciare una transizione di stato per il processo di business quando completa con successo. Seam fornisce uno speciale set di annotazioni per la demarcazione dei task.
Le conversazioni possono essere inestate, con una conversazione che ha posto "dentro" una conversazione più ampia. Questa è una caretteristica avanzata.
Solitamente lo stato della conversazione è mantenuto da Seam in una sessione servlet tra le richieste. Seam implementa dei timeout di conversazione configurabili, che automaticamente distruggono le conversazioni inattive, e quindi assicurano che lo stato mantenuto da una singola sessione utente non cresca senza limiti se l'utente abbandona le conversazioni.
Seam serializza il processo delle richieste concorrenti che prendono posto nello stesso contesto di conversazione long-running, nello stesso processo.
In alternativa Seam può essere configurato per mantenere lo stato conversazionale nel browser.
Un contesto di sessione mantiene lo stato associato alla sessione utente. Mentre ci sono alcuni casi in cui è utile condividere lo stato tra più conversazioni, noi disapproviamo l'uso dei contesti di sessione per mantenere altri componenti diversi da quelli contenenti le informazioni globali sull'utente connesso.
In ambiente portal JSR-168 il contesto sessione rappresenta la sessione portlet.
Il contesto business process mantiene lo stato associato al processo di business long running. Questo stato è gestito e reso persistente dal motore BPM (JBoss jBPM). Il processo di business si prolunga attraverso più interazioni con diversi utenti, quindi questo stato è condiviso tra più utenti, ma in maniera ben definita. Il task corrente determina l'istanza corrente del processo di business, ed il ciclo di vita del processo di business è definito esternamente usando un linguaggio di definizione di processo, quindi non ci sono speciali annotazioni per la demarcazione del processo di business.
Il contesto applicazione è il familiare contesto servlet da specifiche servlet. Il contesto applicazione è principalmente utile per mantenere le informazioni statiche quali dati di configurazione, dati di riferimento i metamodelli. Per esempio, Seam memorizza la sua configurazione ed il metamodello nel contesto applicazione.
Un contesto definisce un namespace, un set di variabili di contesto. Queste lavorano come gli attributi di sessione o richiesta nella specifica servlet. Si può associare qualsiasi valore si voglia alla variabile di contesto, ma solitamente si associano le istanze componenti di Seam alle variabili di contesto.
Quindi all'interno di un contesto un'istanza di componente è identificata dal nome della variabile di contesto (questo è solitamente, ma non sempre, lo stesso del nome del componente). Si può accedere in modo programmatico all'istanza del componente con nome in un particolare scope tramite la classe Contexts
, che fornisce accesso a parecchie istanze legate al thread dell'interfaccia Context
:
User user = (User) Contexts.getSessionContext().get("user");
Si può anche impostare o cambiare il valore associato al nome:
Contexts.getSessionContext().set("user", user);
Solitamente, comunque, si ottengono i componenti da un contesto via injection e si mettono le istanza in un contesto via outjection.
A volte, come sopra, le istanze di un componente sono ottenute da un particolare scope noto. Altre volte, vengono ricercati tutti gli scope stateful in ordine di priorità. Quest'ordine è il seguente:
Contesto Evento
contesto Pagina
contesto Conversazione
contesto Sessione
contesto Processo di Business
contesto Applicazione
Si può eseguire una ricerca prioritaria chiamando Contexts.lookupInStatefulContexts()
. Quando si accede ad un componente via nome da una pagina JSF, serve una priorità di ricerca.
Né il servlet né le specifiche EJB definiscono dei modi per gestire le richieste correnti originate dallo stesso client. Il servlet container semplicemente lascia girare tutti i thread in modo concorrente e lascia la gestione della sicurezza dei thread al codice dell'applicazione. Il container EJB consente di accedere ai componenti stateless e lancia un'eccezione se dei thread multipli accedono ad un bean di sessione stateful.
Questo comportamento potrebbe risultare corretto nel vecchio stile delle applicazioni web, che erano basate su richieste sincrone con granularità fine. Ma per le moderne applicazioni che fanno ampio uso di molte richieste asincrone (AJAX) a granularità fine, la concorrenza è un fattore vitale e deve essere supportato dal modello di programmazione. Seam possiede un layer per la gestione della concorrenza nel suo modello di contesto.
I contesti Seam di sessione e applicazione sono multithread. Seam lascia le richieste concorrenti in un contesto che verrà processato in modo concorrente. I contesti evento e pagina sono per natura a singolo thread. Il contesto business è strettamente multithread, ma in pratica la concorenza è abbastanza rara e questo fatto può essere ignorato per la maggior parte delle volte. Infine Seam forza un modello a singolo thread per conversazione per processo per il contesto conversazione, serializzando le richieste concorrenti nello stesso contesto di conversazione long-running.
Poiché il contesto sessione è multithread, e spesso contiene uno stato volatile, i componenti con scope sessione sono sempre protetti da Seam verso accessi concorrenti fintantoché gli interceptor Seam non vengano disabilitati per quel componente. Se gli interceptor sono disabilitati, allora ogni sicurezza di thread che viene richiesta deve essere implementata dal componente stesso. Seam serializza di default le richieste a bean con scope sessione e JavaBean (e rileva e rompe ogni deadlock che sopravviene). Questo non è il comportamento di default per i componenti con scope applicazione, poiché tali componenti solitamente non mantengono uno stato volatile e poiché la sincronizzazione a livello globale è estremamente dispensiosa. Comunque si può forzare il modello di thread serializzato su un qualsiasi session bean o componente JavaBean aggiungendo l'annotazione @Synchronized
.
Questo modello di concorrenza signifca che i client AJAX possono usare in modo sicuro le sessioni volatili e lo stato conversazionale, senza il bisogno di alcun lavoro speciale da parte dello sviluppatore.
I componenti Seam sono POJO (Plain Old Java Objects). In particolare sono JavaBean o bean enterprise EJB 3.0. Mentre Seam non richiede che i componenti siano EJB e possono anche essere usati senza un container EJB 3.0, Seam è stato progettato con EJB 3.0 in mente ed realizza una profonda integrazione con EJB 3.0. Seam supporta i seguenti tipi di componenti.
Bean di sessione stateless EJB 3.0
Bean di sessione stateful EJB 3.0
Bean entity EJB 3.0 (cioè classi entity JPA)
JavaBeans
EJB 3.0 message-driven beans
Bean Spring (see Capitolo 27, Integrazione con il framework Spring)
I componenti bean di sessione stateless non sono in grado di mantenere lo stato lungo le diverse invocazioni. Quindi solitamente lavorano operando sullo stato di altri componenti in vari contesti Seam. Possono essere usati come action listener JSF, ma non forniscono proprietà ai componenti JSF da mostrare.
I bean di sessione stateless vivono sempre nel contesto stateless.
Si può accedere ai bean di sessione stateless in modo concorrente poiché viene usata una nuova istanza ad ogni richiesta. L'assegnazione di un'istanza alla richiesta è responsabilità del container EJB3 (normalmente le istanze vengono allocate da un pool riutilizzabile, ciò significa che si possono trovare variabili d'istanza contenenti dati da precedenti utilizzi del bean).
I bean di sessione stateless sono i tipi di componenti Seam meno interessanti.
I bean di sessione stateless Seam possono essere istanziati usando Component.getInstance()
o @In(create=true)
. Non dovrebbero essere istanziati direttamente tramite ricerca JNDI o tramite operatore new
.
I componenti bean di sessione stateful sono in grado di mantenere lo stato non solo lungo più invocazioni del bean, ma anche attraverso richieste multiple. Lo stato dell'applicazione che non appartiene al database dovrebbe essere mantenuto dai bean di sessione stateful. Questa è la grande differenza tra Seam e molti altri framework per applicazioni web. Invece di mettere informazioni sulla conversazione corrente direttamente nella HttpSession
, si dovrebbe mantenerla nelle variabili d'istanza di un bean di sessione stateful, che è legato al contesto conversazione. Questo consente a Seam di gestire il ciclo di vita di questo stato per voi e di assicurare che non ci siano collisioni tra lo stato delle diverse conversazioni concorrenti.
I bean di sessione stateful sono spesso usati come action listener JSF ed in qualità di backing bean forniscono proprietà ai componenti JSF da mostrare o per l'invio di form.
Di default i bean di sessione stateful sono legati al contesto conversazione. Non possono mai essere legati ai contesti pagina o stateless.
Richieste concorrenti a bean di sessione stateful con scope sessione sono sempre serializzati da Seam fintantoché gli interceptor di Seam non vengono disabilitati per tale bean.
I bean di sessione stateful Seam possono essere istanziati usando Component.getInstance()
o @In(create=true)
. Non dovrebbero essere istanziati direttamente tramite ricerca JNDI o tramite operatore new
.
Gli entity bean possono essere associati ad una variabile di contesto e funzionare come componenti Seam. Poiché gli entity hanno un'identità persistente in aggiunta alla loro identità contestuale, le istanze entity sono solitamente associate esplicitamente nel codice Java, piuttosto che essere istanziate implicitamente da Seam.
I componenti entity bean non supportano la bijection o la demarcazione di contesto. E neppure l'invocazione della validazione dell'entity bean (trigger).
Gli entity bean non sono solitamente usati come action listener JSF, ma spesso funzionano come backing bean che forniscono proprietà ai componenti JSF per la visualizzazione o la sottomissione di una form. In particolare è comune usare un entity come backing bean, assieme ad un action listener bean di sessione stateless per implementare funzionalità di tipo crea/aggiorna/cancella.
Di default gli entity bean sono associati al contesto conversazione. Non possono mai essere associati al contesto stateless.
Si noti che in un ambiente cluster è meno efficiente associare un entity bean direttamente ad una variabile di contesto con scope conversazione o sessione rispetto a come sarebbe mantenere un riferimento all'entity bean in un bean di sessione stateful. PEr questa ragione non tutte le applicazioni Seam definiscono entity bean come componenti Seam.
I componenti entity bean di Seam possono essere istanziati usando Component.getInstance()
, @In(create=true)
o direttamente usando l'operatore new
.
I Javabean possono essere usati solo come bean di sessione stateless o stateful. Comunque essi non forniscono la funzionalità di un session bean (demarcazione dichiarativa delle transazioni, sicurezza dichiarativa, replicazione clustered efficiente dello stato, persistenza EJB 3.0, metodi timeout, ecc.)
In un capitolo successivo si mostrerà come impiegare Seam ed Hibernate senza un container EJB. In questo caso i componenti sono JavaBean invece di session bean. Si noti comunque che in molti application server è talvolta meno efficiente clusterizzare la conversazione od i componenti Seam Javabean con scope sessione piuttosto che clusterizzare i componenti session bean stateful.
Di default i JavaBean sono legati al contesto evento.
Le richieste concorrenti a Javabean con scope sessione vengono sempre serializzate da Seam.
I componenti JavaBean di Seam possono essere istanziati usando Component.getInstance()
o @In(create=true)
. Non dovrebbero essere istanziati direttamente tramite operatore new
.
I bean message-driven possono funzionare come componenti Seam. Comunque i bean message-driven sono chiamati in modo diverso rispetto agli altri componenti Seam - invece di invocarli tramite variabile di contesto, essi ascoltano i messaggi inviati ad una coda o topic JMS.
I bean message-driven possono non essere associati ad un contesto Seam. E possono non avere accesso allo stato di sessione o conversazione del loro "chiamante". Comunque essi non supportano la bijection e altre funzionalità di Seam.
I bean message-driven non vengono mai istanziati dall'applicazione. Essi vengono istanziati dal container EJB quando viene ricevuto un messaggio.
Per eseguire le sue magie (bijection, demarcazione di contesto, validazione, ecc.) Seam deve intercettare le invocazioni dei componenti. Per i JavaBean, Seam è nel pieno controllo dell'istanziazione del componente e non ha bisogno di alcuna speciale configurazione. Per gli entity bean, l'intercettazione non è richiesta poiché bijection e demarcazione di contesto non sono definite. Per i session bean occorre registrare un interceptor EJB per il componente bean di sessione. Si può impiegare un'annotazione come segue:
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login {
...
}
Ma il miglio modo è definire l'interceptor in ejb-jar.xml
.
<interceptors>
<interceptor>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name
>*</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor
>
Tutti i componenti Seam devono avere un nome. Si può assegnare un nome al componente usando l'annotazione @Name
:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
...
}
Questo nome è il nome del componente Seam e non è relazionato a nessun altro nome definito dalla specifica EJB. Comunque i nomi dei componenti Seam funzionano solo come nomi per i bean gestiti da JSF e si possono ritenere questi due concetti come identici.
@Name
non è il solo modo per definire un nome di componente, ma occorre sempre specificare il nome da qualche parte. Altrimenti nessun'altra annotazione di Seam funzionerà.
Quando Seam istanzia un componente associa la nuova istanza ad una variabile nello scope configurato per il componente che corrisponde al nome del componente. Questo comportamento è identico al modo in cui funzionano i bean gestiti da JSF, tranne che Seam consente di configurare questa mappatura usando le annotazioni anziché XML. Si può anche associare via codice un componente ad una variabile di contesto. Questo è utile se un particolare componente serve più di un ruolo nel sistema. Per esempio lo User
correntemente loggato può essere associato alla variabile di contesto sessione currentUser
, mentre uno User
che è soggetto ad alcune funzionalità di amministrazione può essere associato alla variabile di contesto conversazione user
. Attenzione poiché attraverso l'assegnamento programmatico è possibile sovrascrivere la variabile di contesto che ha un riferimento ad un componente Seam, cosa che può creare parecchi problemi.
Per applicazioni estese e per i componenti predefiniti di seam, vengono spesso impiegati i nomi qualificati dei componenti per evitare conflitti di nome.
@Name("com.jboss.myapp.loginAction")
@Stateless
public class LoginAction implements Login {
...
}
Si può utilizzare un nome qualificato di componente sia nel codice Java sia nell'expression language JSF:
<h:commandButton type="submit" value="Login"
action="#{com.jboss.myapp.loginAction.login}"/>
Poiché questo è noioso, Seam fornisce anche un modo per nominare in altro modo un nome qualificato in un nome semplice. Si aggiunga una linea come questa al file components.xml
:
<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>
Tutti i componenti Seam predefiniti hanno nomi qualificati ma possono essere acceduti attraverso i loro nomi non qualificati grazie alla funzionalità di Seam dell'importazione del namespace. Il file components.xml
incluso nei jar di Seam definisce i seguenti namespace.
<components xmlns="http://jboss.com/products/seam/components"> <import>org.jboss.seam.core</import> <import>org.jboss.seam.cache</import> <import>org.jboss.seam.transaction</import> <import>org.jboss.seam.framework</import> <import>org.jboss.seam.web</import> <import>org.jboss.seam.faces</import> <import>org.jboss.seam.international</import> <import>org.jboss.seam.theme</import> <import>org.jboss.seam.pageflow</import> <import>org.jboss.seam.bpm</import> <import>org.jboss.seam.jms</import> <import>org.jboss.seam.mail</import> <import>org.jboss.seam.security</import> <import>org.jboss.seam.security.management</import> <import>org.jboss.seam.security.permission</import> <import>org.jboss.seam.captcha</import> <import>org.jboss.seam.excel.exporter</import> <!-- ... ---> </components>
Quando si tenta di risolvere un nome non qualificato, Seam controlla in ordine ciascuno dei namespace. Si possono includere namespace addizionali nel file components.xml
per namespace specifici dell'applicazione.
Si può sovrascrivere lo scope di default (contesto) di un componente usando l'annotazione @Scope
. Questo consente di definire a quale contesto è associata un'istanza di componente, quando questo viene istanziato da Seam.
@Name("user")
@Entity
@Scope(SESSION)
public class User {
...
}
org.jboss.seam.ScopeType
definisce un'enumeration dei possibili scope.
Alcune classi componenti Seam possono svolgere più di un ruolo nel sistema. Per esempio si haspesso la classe User
che viene usata come componente con scope sessione e che rappresenta l'utente corrente, ma nelle schermate di amministrazione utente viene usato come componente con scope conversazione. L'annotazione @Role
consente di definire un ruolo addizionale per un componente, con scope differente — consente di associare la stessa classe componente a variabili di contesto differenti. (Qualsiasi istanza componente Seam può essere associata a variabili di contesto multiple, ma questo è consentito a livello di classe e per sfruttare l'autoistanziamento.)
@Name("user")
@Entity
@Scope(CONVERSATION)
@Role(name="currentUser", scope=SESSION)
public class User {
...
}
L'annotazione @Roles
consente di specificare tanti ruoli quanti se ne vuole.
@Name("user")
@Entity
@Scope(CONVERSATION)
@Roles({@Role(name="currentUser", scope=SESSION),
@Role(name="tempUser", scope=EVENT)})
public class User {
...
}
Come molti buoni framework, Seam si nutre del proprio cibo ed è implementato come set di interceptor (vedere più avanti) predefiniti e di componenti Seam. Questo consente facilmente alle applicazioni di interagire a runtime con i componenti predefiniti o anche personalizzare le funzionalità base di Seam sostituendo i componenti predefiniti con implementazioni ad hoc. I componenti predefiniti sono definiti nel namespace di Seam org.jboss.seam.core
e nel pacchetto Java con lo stesso nome.
I componenti predefiniti possono essere iniettati, come ogni altro componente Seam, ma possono anche fornire dei metodi statici instance()
di convenienza.
FacesMessages.instance().add("Welcome back, #{user.name}!");
Dependency injection o inversione del controllo è ora un concetto familiare allamaggior parte degli sviluppatori Java. La dependency injection consente ad un componente di ottenere un riferimento ad un altro componente facendo "iniettare" dal container l'altro componente in un metodo setter o variabile istanza. In tutte le implementazioni di dependency injection che abbiamo visto, l'injection avviene quando viene costruito il componente, ed il riferimento non cambia durante il ciclo di vita dell'istanza del componente. Per i componenti stateless questo è ragionevole. Dal punto di vista del client tutte le istanzedi un particolare componente stateless sono intercambiabili. Dall'altro lato Seam enfatizza l'uso di componenti stateful. Quindi la tradizionale dependency injection non è più un costrutto utile. Seam introduce la nozione di bijection come generalizzazione dell'injection. In contrasto all'injection, la bijection è:
contestuale - la bijection è usata per assemblare i componenti stateful da vari contesti differenti (un componente da un contesto più "ampio" può anche fare riferimento ad un componente di un contesto più "ristretto")
bidirezionale - i valori sono iniettati da variabili di contesto negli attributi del componente invocato, ed anche outjected da attributi di componenti nel contesto, consentendo al componente di essere invocato per manipolare i valori delle variabili contestuali semplicemente impostando le proprie variabili d'istanza
dimanica - poiché il valore delle variabili contestuali cambia neltempo, e poiché i componenti Seam sono stateful, la bijection avviene ogni volta che viene invocato il componente
In sostanza la bijection consente di rinominare le variabili di contesto in variabili istanza del componente, specificando che il valore della variabile d'istanza sia iniettata, outjected o entrambe, Certamente vengono usate annotazioni per abilitare la bijection.
L'annotazione @In
specifica che un valore venga iniettato, sia in una variabile istanza:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In User user;
...
}
o nel metodo setter:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@In
public void setUser(User user) {
this.user=user;
}
...
}
Di default Seam esegue una ricerca prioritaria di tutti i contesti, usando il nome della proprietà o variabile d'istanza che viene iniettata. Si può specificare esplicitamente il nome della varibile di contesto, usando, per esempio, @In("currentUser")
.
Se si vuole che Seam crei un'istanza del componente quando non esiste un'istanza di componente associata alla variabile di contesto, occorre specificare @In(create=true)
. Se il valore è opzionale (può essere null), specificare @In(required=false)
.
Per alcuni componenti può essere ripetitivo dove specificare @In(create=true)
ogni volta che sono usati. In questi casi si può annotare il componente con @AutoCreate
, e quindi questo verrà creato, quando necessario, senza dover escplicitare create=true
.
Si può anche iniettare il valore di un'espressione:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In("#{user.username}") String username;
...
}
I valori iniettati sono disiniettati (cioè impostati a null
) immediatamente dopo il completamento del metodo e dell'outjection.
(Maggiori informazioni sul ciclo di vita dei componenti e su injection nel prossimo capitolo.)
L'annotazione @Out
specifica che occorre eseguire l'outjection di un attributo, oppure da una variabile d'istanza:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@Out User user;
...
}
o dal metodo getter:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@Out
public User getUser() {
return user;
}
...
}
Di un attributo si può fare sia l'injection sia l'outjection:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In @Out User user;
...
}
o:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@In
public void setUser(User user) {
this.user=user;
}
@Out
public User getUser() {
return user;
}
...
}
I componenti di Seam session ed entity bean supportano tutte le chiamate del ciclo di vita EJB3.0 (@PostConstruct
, @PreDestroy
, ecc). Ma Seam supporta anche l'uso di queste chiamate con i componenti JavaBean. Comunque, poiché queste annotazioni non sono disponibile in un ambiente J2EE, Seam definisce due chiamate aggiuntive al ciclo di vita del componente, equivalenti a @PostConstruct
e @PreDestroy
.
Il metodo @Create
viene chiamato dopo che Seam istanzia un componente. I componenti possono definire solo un metodo @Create
.
Il metodo @Destroy
viene chiamato quando termina il contesto a cui è legato il componente Seam. I componenti possono definire solo un metodo @Destroy
.
In aggiunta, i componenti bean stateful session devono definire un metodo senza paramentri annotato con @Remove
. Questo metodo viene chiamato da Seam quando termina il contesto.
Infine, un'annotazione collegata è l'annotazione @Startup
, che può essere applicata ad ogni componente con scope applicazione o sessione. L'annotazione @Startup
dice a Seam di istanziare immediatamente il componente, quand inizia il contesto, invece di aspettare che venga referenziato per primo dal client. E' possibile controllare l'ordine di istanziamento dei componenti startup specificando @Startup(depends={....})
.
L'annotazione @Install
consente di controllare l'installazione condizionale di componenti che sono richiesti in alcuni scenari di deploy e non i altri. Questa è utile se:
Si vogliono costruire dei mock per qualche componente infrastrutturale da testare.
Si vuole cambiare l'implementazione di un componente in certi scenari di deploy.
Si vogliono installare alcuni componenti solo se le loro dipendenze sono disponibili (utile per gli autori di framework).
@Install
funziona consentendo di specificare precedence e dependencies.
La precedenza di un componente è un numero che Seam usa per decidere quale componente installare quando ci sono più classi con lo stesso nome componente nel classpath. Seam sceglierà il componente con la precedenza più elevata. Ci sono alcuni valori di precedenza predefiniti (in ordine ascendente):
BUILT_IN
— i componenti con più bassa precedenza sono i componenti predefiniti in Seam.
FRAMEWORK
— i componenti definiti da framework di terze parti possono sovrascrivere i componenti predefiniti, ma vengono sovrascritti dai componenti applicazione.
APPLICATION
— la precedenza di default. Questo è appropriato per i componenti delle applicazioni più comuni.
DEPLOYMENT
— per i componenti applicazione che sono specifici per un deploy.
MOCK
— per gli oggetti mock usati in fase di test.
Si supponga di avere un componente chiamato messageSender
che dialoga con una coda JMS.
@Name("messageSender")
public class MessageSender {
public void sendMessage() {
//do something with JMS
}
}
Nei test d'unità non si ha una coda JMS disponibile, e quindi si vuole costruire uno stub del metodo. Si creerà un componente mock che esiste nel classpath quando girano i test d'unità, ma non viene mai deployato con l'applicazione:
@Name("messageSender")
@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
public void sendMessage() {
//do nothing!
}
}
La precedenza
aiuta Seam a decide quale versione usare quando vengono trovati entrambi i componenti nel classpath.
Sarebbe bello poter controllare esattamente quali classi sono nel classpath. Ma se si sta scrivendo un framework riusabile con molte dipendenze, non si vuole dover suddividere tale framework in molti jar. Si vuole poter decidere quali componenti installare a seconda di quali altri componenti sono installati, e a seconda di quali classi sono disponibili nel classpath. Anche l'annotazione @Install
controlla questa funzionalità. Seam utilizza questo meccanismo internamente per abilitare l'installazione condizionale di molti componenti predefiniti. Comunque con ogni probabilità non lo si utilizzerà nelle applicazioni.
Chi non è nauseato dal vedere codice incasinato come questo?
private static final Log log = LogFactory.getLog(CreateOrderAction.class);
public Order createOrder(User user, Product product, int quantity) {
if ( log.isDebugEnabled() ) {
log.debug("Creating new order for user: " + user.username() +
" product: " + product.name()
+ " quantity: " + quantity);
}
return new Order(user, product, quantity);
}
E' difficile immaginare come possa essere più prolisso il codice per un semplice messaggio di log. Ci sono più linee di codice per il loggin che per la business logic! Ci meravigliamo come la comunità Java non abbia fatto qualcosa di meglio in 10 anni.
Seam fornisce un'API per il logging che semplifica in modo significativo questo codice:
@Logger private Log log;
public Order createOrder(User user, Product product, int quantity) {
log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
return new Order(user, product, quantity);
}
Non importa se si dichiara la variabile log
statica o no — funzionerà in entrambi i modi, tranne per i componenti entity bean che richiedono la variabile log
statica.
Si noti che non occorre il nioso controllo if ( log.isDebugEnabled() )
, poiché la concatenazione della stringa avviene dentro il metodo debug()
. Si noti inoltre che in genere non occorre specificare esplicitamente la categoria di log, poiché Seam conosce quale componente sta iniettando dentro Log
.
Se User
e Product
sono componenti Seam disponibili nei contesti correnti, funziona ancora meglio:
@Logger private Log log;
public Order createOrder(User user, Product product, int quantity) {
log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
return new Order(user, product, quantity);
}
Il logging di Seam sceglie automaticamente se inviare l'output a log4j o al logging JDK. Se log4j è nel classpath, Seam lo userà. Se non lo è, Seam userà il logging JDK.
Molti application server forniscono un'incredibile implementazione inesatta del clustering HttpSession
, dove i cambiamenti allo stato di oggetti mutabili legati alla sessione sono replicati solamente quando l'applicazione chiama esplicitamente setAttribute()
. Questo è fonte di bug che non possono essere testati in modo efficace in fase di sviluppo, poiché si manifestano solo quando avviene un failover. Inoltre, il messaggio di replicazione contiene l'intero grafo oggetto serializzato associato all'attributo sessione, che è inefficiente.
Certamente i bean EJB session stateful devono eseguire un dirty checking automatico e la replicazione dello stato mutabile, ed un sofisticato container EJB può introdurre ottimizzazioni quali la replicazione a livello di attributo. Sfortunatamente, non tutti gli utenti Seam hanno la fortuna di lavorare in un ambiente che supporta EJB 3.0. Quindi per i componenti JavaBean ed entity bean con scope sessione e conversazione, Seam fornisce un layer extra di gestione dello stato cluster-safe sopra il clustering di sessione del web container.
Per i componenti JavaBean con scope sessione o conversazione, Seam forza automaticamente la replicazione ad avvenire chiamando setAttribute()
una sola volta per ogni richiesta in cui il componente viene invocato dall'applicazione. Certo che questa strategia è inefficiente per i componenti per lo più letti. Si può controllare questo comportamento implementando l'interfaccia org.jboss.seam.core.Mutable
, od estendendo org.jboss.seam.core.AbstractMutable
, e scrivendo la propria logica di dirty-checking dentro il componente. Per esempio,
@Name("account")
public class Account extends AbstractMutable
{
private BigDecimal balance;
public void setBalance(BigDecimal balance)
{
setDirty(this.balance, balance);
this.balance = balance;
}
public BigDecimal getBalance()
{
return balance;
}
...
}
O si può usare l'annotazione @ReadOnly
per ottenere un effetto simile:
@Name("account")
public class Account
{
private BigDecimal balance;
public void setBalance(BigDecimal balance)
{
this.balance = balance;
}
@ReadOnly
public BigDecimal getBalance()
{
return balance;
}
...
}
Per i componenti entity bean con scope sessione o conversazione, Seam forza automaticamente la replicazione ad avvenire chiamando setAttribute()
una sola volta per ogni richiesta, a menoché l'entity (con scope conversazione) sia associato ad un contesto di persistenza gestito da Seam, nel qual caso non occorre alcuna replicazione. Questa strategia non è necessariamente efficiente, quindi gli entity bean con scope sessione o conversazione dovrebbero essere usati con cautela. Si può sempre scrivere un componente session bean stateful o JavaBean per "gestire" l'istanza entity bean. Per esempio,
@Stateful
@Name("account")
public class AccountManager extends AbstractMutable
{
private Account account; // an entity bean
@Unwrap
public Account getAccount()
{
return account;
}
...
}
Si noti che la classe EntityHome
nel framework Seam fornisce un eccellente esempio di gestione di istanza entity bean usando un componente Seam.
Spesso occorre lavorare con oggetti che non sono componenti Seam. Ma si vuole comunque essere in grado di iniettarli nei componenti usando @In
ed usarli nelle espressioni di value e method binding, ecc. A volte occorre anche legarli al ciclo di vita del contesto Seam (per esempio @Destroy
). Quindi i contesti Seam possono contenere oggetti che non sono componenti Seam, e Seam fornisce un paio di funzionalità interessanti che facilitano il lavoro con oggetti non componenti associati ai contesti.
Il pattern del componente factory lascia agire un componente Seam come istanziatore per un oggetto non componente. Un metodo factory verrà chiamato quando viene referenziata una variabile di contesto, ma nessun valore è associato ad essa. Si definiscono metodi factory usando l'annotazione @Factory
. Il metodo factory associa il valore alla variabile di contesto, e determina lo scope del valore associato. Ci sono due stili di metodi factory. Il primo stile restituisce un valore, che è viene associato al contesto da Seam:
@Factory(scope=CONVERSATION)
public List<Customer
> getCustomerList() {
return ... ;
}
Il secondo stile è un metodo di tipo void
che associa il valore alla variabile di contesto stessa:
@DataModel List<Customer
> customerList;
@Factory("customerList")
public void initCustomerList() {
customerList = ... ;
}
In entrambi i casi il metodo factory viene chiamato quando si referenzia la variabile di contesto customerList
ed il suo valore è null, e quindi non ha sono ulteriori parti in gioco nel ciclo di vita del valore. Un pattern ancora più potente è il pattern del componente manager. In questo caso c'è un componente Seam che è associato ad una variabile di contesto, e gestisce il valore di tale variabile, rimanendo invisibile ai client.
Un componente manager è un qualsiasi componente con un metodo @Unwrap
. Questo metodo restituisce il valore che sarà visibile ai client, e viene chiamato ogni volta che viene referenziata una variabile di contesto.
@Name("customerList")
@Scope(CONVERSATION)
public class CustomerListManager
{
...
@Unwrap
public List<Customer
> getCustomerList() {
return ... ;
}
}
Il pattern del componente managerè utile specialmente se si ha un oggetto in cui serve un maggior controllo sul ciclo di vita. Per esempio, se si ha un oggetto pesante che necessita di un'operazione di cleanup quando termina il contesto, si potrebbere annotare l'oggetto con @Unwrap
ed eseguire il cleanup nel metodo @Destroy
del componente manager.
@Name("hens")
@Scope(APPLICATION)
public class HenHouse
{
Set<Hen
> hens;
@In(required=false) Hen hen;
@Unwrap
public List<Hen
> getHens()
{
if (hens == null)
{
// Setup our hens
}
return hens;
}
@Observer({"chickBorn", "chickenBoughtAtMarket"})
public addHen()
{
hens.add(hen);
}
@Observer("chickenSoldAtMarket")
public removeHen()
{
hens.remove(hen);
}
@Observer("foxGetsIn")
public removeAllHens()
{
hens.clear();
}
...
}
Qua il componente gestito osserva diversi eventi che cambiano l'oggetto sottostante. Il componente stesso gestisce queste azioni, e poiché l'oggetto è unwrap ad ogni accesso, viene fornita una vista consistente.