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 statto 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à.
Whenever Seam instantiates a component, it binds the new instance to a variable in the scope configured for the component that matches the component name. This behavior is identical to how JSF managed beans work, except that Seam allows you to configure this mapping using annotations rather than XML. You can also programmatically bind a component to a context variable. This is useful if a particular component serves more than one role in the system. For example, the currently logged in User
might be bound to the currentUser
session context variable, while a User
that is the subject of some administration functionality might be bound to the user
conversation context variable. Be careful, though, because through a programmatic assignment, it's possible to overwrite a context variable that has a reference to a Seam component, potentially confusing matters.
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 {
...
}
We may use the qualified component name both in Java code and in JSF's expression language:
<h:commandButton type="submit" value="Login"
action="#{com.jboss.myapp.loginAction.login}"/>
Since this is noisy, Seam also provides a means of aliasing a qualified name to a simple name. Add a line like this to the components.xml
file:
<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>
All of the built-in Seam components have qualified names but can be accessed through their unqualified names due to the namespace import feature of Seam. The components.xml
file included in the Seam JAR defines the following namespaces.
<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>
When attempting to resolve an unqualified name, Seam will check each of those namespaces, in order. You can include additional namespaces in your application's components.xml
file for application-specific namespaces.
We can override the default scope (context) of a component using the @Scope
annotation. This lets us define what context a component instance is bound to, when it is instantiated by Seam.
@Name("user")
@Entity
@Scope(SESSION)
public class User {
...
}
org.jboss.seam.ScopeType
definisce un'enumeration dei possibili scope.
Some Seam component classes can fulfill more than one role in the system. For example, we often have a User
class which is usually used as a session-scoped component representing the current user but is used in user administration screens as a conversation-scoped component. The @Role
annotation lets us define an additional named role for a component, with a different scope — it lets us bind the same component class to different context variables. (Any Seam component instance may be bound to multiple context variables, but this lets us do it at the class level, and take advantage of auto-instantiation.)
@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 {
...
}
Like many good frameworks, Seam eats its own dogfood and is implemented mostly as a set of built-in Seam interceptors (see later) and Seam components. This makes it easy for applications to interact with built-in components at runtime or even customize the basic functionality of Seam by replacing the built-in components with custom implementations. The built-in components are defined in the Seam namespace org.jboss.seam.core
and the Java package of the same name.
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 or inversion of control is by now a familiar concept to most Java developers. Dependency injection allows a component to obtain a reference to another component by having the container "inject" the other component to a setter method or instance variable. In all dependency injection implementations that we have seen, injection occurs when the component is constructed, and the reference does not subsequently change for the lifetime of the component instance. For stateless components, this is reasonable. From the point of view of a client, all instances of a particular stateless component are interchangeable. On the other hand, Seam emphasizes the use of stateful components. So traditional dependency injection is no longer a very useful construct. Seam introduces the notion of bijection as a generalization of injection. In contrast to injection, bijection is:
contextual - bijection is used to assemble stateful components from various different contexts (a component from a "wider" context may even have a reference to a component from a "narrower" context)
bidirectional - values are injected from context variables into attributes of the component being invoked, and also outjected from the component attributes back out to the context, allowing the component being invoked to manipulate the values of contextual variables simply by setting its own instance variables
dynamic - since the value of contextual variables changes over time, and since Seam components are stateful, bijection takes place every time a component is invoked
In essence, bijection lets you alias a context variable to a component instance variable, by specifying that the value of the instance variable is injected, outjected, or both. Of course, we use annotations to enable bijection.
The @In
annotation specifies that a value should be injected, either into an instance variable:
@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;
}
...
}
By default, Seam will do a priority search of all contexts, using the name of the property or instance variable that is being injected. You may wish to specify the context variable name explicitly, using, for example, @In("currentUser")
.
If you want Seam to create an instance of the component when there is no existing component instance bound to the named context variable, you should specify @In(create=true)
. If the value is optional (it can be null), specify @In(required=false)
.
For some components, it can be repetitive to have to specify @In(create=true)
everywhere they are used. In such cases, you can annotate the component @AutoCreate
, and then it will always be created, whenever needed, even without the explicit use of 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.
(There is much more information about component lifecycle and injection in the next chapter.)
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;
}
...
}
Session bean and entity bean Seam components support all the usual EJB 3.0 lifecycle callback (@PostConstruct
, @PreDestroy
, etc). But Seam also supports the use of any of these callbacks with JavaBean components. However, since these annotations are not available in a J2EE environment, Seam defines two additional component lifecycle callbacks, equivalent to @PostConstruct
and @PreDestroy
.
The @Create
method is called after Seam instantiates a component. Components may define only one @Create
method.
The @Destroy
method is called when the context that the Seam component is bound to ends. Components may define only one @Destroy
method.
In addition, stateful session bean components must define a method with no parameters annotated @Remove
. This method is called by Seam when the context ends.
Finally, a related annotation is the @Startup
annotation, which may be applied to any application or session scoped component. The @Startup
annotation tells Seam to instantiate the component immediately, when the context begins, instead of waiting until it is first referenced by a client. It is possible to control the order of instantiation of startup components by specifying @Startup(depends={....})
.
The @Install
annotation lets you control conditional installation of components that are required in some deployment scenarios and not in others. This is useful if:
You want to mock out some infrastructural component in tests.
You want change the implementation of a component in certain deployment scenarios.
You want to install some components only if their dependencies are available (useful for framework authors).
@Install
works by letting you specify precedence and dependencies.
The precedence of a component is a number that Seam uses to decide which component to install when there are multiple classes with the same component name in the classpath. Seam will choose the component with the higher precendence. There are some predefined precedence values (in ascending order):
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.
Suppose we have a component named messageSender
that talks to a JMS queue.
@Name("messageSender")
public class MessageSender {
public void sendMessage() {
//do something with JMS
}
}
In our unit tests, we don't have a JMS queue available, so we would like to stub out this method. We'll create a mock component that exists in the classpath when unit tests are running, but is never deployed with the application:
@Name("messageSender")
@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
public void sendMessage() {
//do nothing!
}
}
The precedence
helps Seam decide which version to use when it finds both components in the classpath.
This is nice if we are able to control exactly which classes are in the classpath. But if I'm writing a reusable framework with many dependecies, I don't want to have to break that framework across many jars. I want to be able to decide which components to install depending upon what other components are installed, and upon what classes are available in the classpath. The @Install
annotation also controls this functionality. Seam uses this mechanism internally to enable conditional installation of many of the built-in components. However, you probably won't need to use it in your application.
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);
}
It is difficult to imagine how the code for a simple log message could possibly be more verbose. There is more lines of code tied up in logging than in the actual business logic! I remain totally astonished that the Java community has not come up with anything better in 10 years.
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.
Note that we don't need the noisy if ( log.isDebugEnabled() )
guard, since string concatenation happens inside the debug()
method. Note also that we don't usually need to specify the log category explicitly, since Seam knows what component it is injecting the Log
into.
If User
and Product
are Seam components available in the current contexts, it gets even better:
@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);
}
Seam logging automagically chooses whether to send output to log4j or JDK logging. If log4j is in the classpath, Seam with use it. If it is not, Seam will use JDK logging.
Many application servers feature an amazingly broken implementation of HttpSession
clustering, where changes to the state of mutable objects bound to the session are only replicated when the application calls setAttribute()
explicitly. This is a source of bugs that can not effectively be tested for at development time, since they will only manifest when failover occurs. Furthermore, the actual replication message contains the entire serialized object graph bound to the session attribute, which is inefficient.
Of course, EJB stateful session beans must perform automatic dirty checking and replication of mutable state and a sophisticated EJB container can introduce optimizations such as attribute-level replication. Unfortunately, not all Seam users have the good fortune to be working in an environment that supports EJB 3.0. So, for session and conversation scoped JavaBean and entity bean components, Seam provides an extra layer of cluster-safe state management over the top of the web container session clustering.
For session or conversation scoped JavaBean components, Seam automatically forces replication to occur by calling setAttribute()
once in every request that the component was invoked by the application. Of course, this strategy is inefficient for read-mostly components. You can control this behavior by implementing the org.jboss.seam.core.Mutable
interface, or by extending org.jboss.seam.core.AbstractMutable
, and writing your own dirty-checking logic inside the component. For example,
@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;
}
...
}
For session or conversation scoped entity bean components, Seam automatically forces replication to occur by calling setAttribute()
once in every request, unless the (conversation-scoped) entity is currently associated with a Seam-managed persistence context, in which case no replication is needed. This strategy is not necessarily efficient, so session or conversation scope entity beans should be used with care. You can always write a stateful session bean or JavaBean component to "manage" the entity bean instance. For example,
@Stateful
@Name("account")
public class AccountManager extends AbstractMutable
{
private Account account; // an entity bean
@Unwrap
public Account getAccount()
{
return account;
}
...
}
Note that the EntityHome
class in the Seam Application Framework provides a great example of managing an entity bean instance using a Seam component.
We often need to work with objects that are not Seam components. But we still want to be able to inject them into our components using @In
and use them in value and method binding expressions, etc. Sometimes, we even need to tie them into the Seam context lifecycle (@Destroy
, for example). So the Seam contexts can contain objects which are not Seam components, and Seam provides a couple of nice features that make it easier to work with non-component objects bound to contexts.
The factory component pattern lets a Seam component act as the instantiator for a non-component object. A factory method will be called when a context variable is referenced but has no value bound to it. We define factory methods using the @Factory
annotation. The factory method binds a value to the context variable, and determines the scope of the bound value. There are two styles of factory method. The first style returns a value, which is bound to the context by 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 both cases, the factory method is called when we reference the customerList
context variable and its value is null, and then has no further part to play in the lifecycle of the value. An even more powerful pattern is the manager component pattern. In this case, we have a Seam component that is bound to a context variable, that manages the value of the context variable, while remaining invisible to clients.
A manager component is any component with an @Unwrap
method. This method returns the value that will be visable to clients, and is called every time a context variable is referenced.
@Name("customerList")
@Scope(CONVERSATION)
public class CustomerListManager
{
...
@Unwrap
public List<Customer
> getCustomerList() {
return ... ;
}
}
The manager component pattern is especially useful if we have an object where you need more control over the lifecycle of the component. For example, if you have a heavyweight object that needs a cleanup operation when the context ends you could @Unwrap
the object, and perform cleanup in the @Destroy
method of the manager component.
@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();
}
...
}
Here the managed component observes many events which change the underlying object. The component manages these actions itself, and because the object is unwrapped on every access, a consistent view is provided.