SeamFramework.orgCommunity Documentation
Mutable
et @ReadOnly
Les deux concepts centraux dans Seam sont la notion de contexte et la notion de composant. Les composants sont des objets avec état, habituellement des EJBs, et une instance d’un composant est associé avec un contexte, et défini par un nom dans ce contexte. La Bijection fourni un mécanisme pour nommer les composants par un alias interne (les variables d’instances) dans les noms du contexte, autorisant des arbres de composants a être dynamiquement assemblés et réassemblés par Seam.
Commençons par la description des contextes existant dans Seam.
Les contextes de Seam sont créés et détruit par le serveur de squelette d’application (framework) via des appels explicite à l’API Java. Les contextes sont habituellement implicites. Dans certains cas, malgré tout, les contextes sont spécifiés via des annotations.
Les contextes de base de Seam
Le contexte sans état
Le contexte évènementiel (autrement dir, de requête)
Le contexte de Page
Le contexte conversatinnel
Le contexte de Session
Le contexte de processus métier
Le contexte d'Application
Nous pouvons reconnaître certain de ces contexte de part les spécifications des servlets autour des servlets. Malgré cela, deux d’entre elles devraient être nouvelle pour vous : contexte conversationnel, et contexte de processus métier. Une des raisons pour que le gestionnaire d’état qui est dans les applications web, soit si fragile et un nid à erreurs c’est que trois des contextes livrés (requête, session et application) ne sont pas particulièrement utile du point de vue de la logique métier. Une session d’authentification d’un utilisateur, par exemple, est construite presque arbitrairement dans une application de flot de travail. Ainsi, les composants Seam sont bornés à la conversation ou aux contextes des processus métier, ainsi ils sont les contextes qui ont le plus de sens en termes d’application.
Ayons un regard sur chacun de ces contextes l'un après l'autre.
Les composants qui sont réellement sans état (les beans sessions sans état, principalement) vivent toujours dans un contexte sans état (ce qui est basiquement une absence de contexte). Les composants sans état ne sont pas vraiment intéressant et sont dénigrés pour ne pas être orienté objet. Malgré cela, ils sont importants, très souvent utile et aussi une part important de toute application Seam.
Le contexte d’évènement est le contexte avec état le plus "réduit", et est la généralisation de la notion du contexte d’une requête web pour couvrir les autres types d’évènements. Malgré cela, le contexte d’évènement associé avec un cycle de vue dans une requête JSF est l’exemple le plus important d’un contexte d’évènement, et le seul qui va fonctionner le plus souvent. Les composants associés avec un contexte d’évènement sont détruit à la fin de la requête, mais leurs état est disponible et bien définie pour au moins le cycle de vie de la requête.
Quand vous invoquez un composant Seam via RMI ou Seam Remoting, le contexte d’évènement est créé et détruit juste pour l’invocation.
Le contexte de page vous permet d’associer un état avec une instance particulière d’un page à rendre. Vous pouvez initialiser un état dans votre écouteur d’évènement ou au moment ou la page est en train d'être rendue et ensuite avoir un accès sur elle depuis tout évènement qui a comme origine cette page. Ceci est spécialement utile pour la fonctionnalité comme une liste cliquable quand la liste est controlée par une donnée changeante côté serveur. L’état est actuellement sérialisé vers le client, donc cette construction est extrêmement robuste dans le respect d’opération multi fenêtre et du bouton précédent.
Le contexte de conversation est un vraiment un concept central dans Seam. Une conversation est une unité de travail du point de vue de l’utilisateur. Elle peut s’étendre sur plusieurs interactions de l’utilisateur, plusieurs requêtes, et plusieurs transactions de base de données. Mais pour l’utilisateur, une conversation résout un seul problème. Par exemple, "réserver un hôtel", "valider un contact", "créer un bon de commande" sont toutes des conversations. Vous devriez apprécier de penser en termes d’implémentation de la conversation comme un seul "cas d’utilisation" ou une "exemple d’utilisation" mais la relation n’est pas nécessairement aussi directe.
Une conversation retient un état associé avec "ce que l’utilisateur est en train de faire maintenant dans cette fenêtre". Un simple utilisateur peut avoir de multiples conversations en cours à tout moment dans le temps, habituellement dans de multiples fenêtres. Le contexte de conversation nous permet de nous assurer que cet état provenant de différentes conversations ne peut se caramboler et causer des bugs.
Il est possible que cela vous prennes du temps de maitriser la façon de penser l’application en termes de conversations. Mais une fois que vous avez l’habitude de le faire, nous pensons que vous allez adorer cette notion, et que vous ne serez plus capable de ne jamais plus penser en termes de conversations!
Quelques conversations perdurent dans une simple requête. Les conversations qui s’étendent sur plusieurs requêtes doivent être bien démarquées en utilisant les annotations fournies par Seam.
Quelques conversations sont aussi des tâches. une tache est une conversation ce qui a un sens en termes de processus métier à exécution longue et a le potentiel de déclencher une transition d’état pour un processus métier quand elle réussit à se terminer. Seam fournis une série d’annotations spéciales pourdifférencie cette tâche.
Les conversations doivent être reliées, avec une conversation prenant sa place "à l’intérieur" d’une plus grande conversation. Ceci est une fonctionnalité avancée.
Habituellement, l’état de la conversation est actuellement maintenu par Seam dans la session servlet entre les requêtes. Seam implémente un delais de péremption configurable, détruisant automatiquement les conversations inactives et ainsi s’assurant que l’état maintenu par une session de connexion d’un seul utilisateur ne grandi hors limitation si l’utilisateur abandonne les conversations.
Seam sérialise les requêtes de processus concurent qui ont lieu dans le même contexte de conversation à exécution longue, dans le même processus.
Autre alternative, Seam peut être configuré pour converser l’état conversationnel dans le navigateur du client.
Un contexte de session conserve un état associé avec une session de connexion utilisateur. Pour les quelques cas où il est utilise de partager l’état entre plusieurs conversation, nous libèrons habituellement l’utilisation du contexte de session pour stocker les composants autre que les informations globales à propos de la connection de l’utilisateur..
Dans l’environnement portail JSR168, le contexte de session représente la session portail.
Le contexte de processus métier stocke l’état associé au processus métier à exécution longue. Cet état est géré et est rendu persistant par le moteur BMP (JBoss JBPM). Le processus métier s’étend sur de multiples interactions avec de multiples utilisateurs, donc cet état est partagé entre plusieurs utilisateurs mais de manière parfaitement définie. La tache courante détermine l’instance du processus métier courante et le cycle de vie du processus métier est définie en externe en utilisant un language de définition de processus, donc il n’y a pas d’annotatation spéciale pour la séparation du processus métier.
Le contexte d’application est le contexte de servlet familier de la spécification servlet. Le contexte d’application est principalement utilisé pour stocker l’information statique comme des données de configuration, des données de référence ou des méta-modèles. Par exemple, Seam stocke sa propre configuration et son méta-modèle dans le contexte d’application.
Un contexte défini un espace de nom, un ensemble de variables de contexte. Ceci fonctionne à peu prêt comme des attributs de session ou de requête dans la spécification servlet. Vous devez faire correspondre n’importe quelle valeur que vous voulez à une variable de contexte, mais habituellement nous faisons correspondre les instances de composant Seam à des variables de contexte.
Donc, dans un contexte, une instance de composant est identifiée par le nom de la variable de contexte (ceci est habituellement, mais pas toujours, la même chose que le nom du composant). Vous pouvez par programmation accéder à l’instance du composant nommée dans une étendue particulière via la classe Contexts
, qui fournis un accès aux différentes instances reliées par thread de l’interace Context
:
User user = (User) Contexts.getSessionContext().get("user");
Vous pouvez aussi définir ou changer la valeur associé avec son nom :
Contexts.getSessionContext().set("user", user);
Habituellement, malgré tout, nous obtenons les composants depuis le contexte via une injection, et mettons les instances de composants dans le contexte via une extrusion.
Parfois, comme ci-dessus, les instances des composants sont obtenus depuis une étendue connus particulière. D’autre fois, tous les étendues avec états sont parcourues, dans un ordre de priorité. Cet ordre est indiqué ci-dessous:
Contexte d'évènement
Le contexte de Page
Le contexte conversatinnel
Le contexte de Session
Le contexte de processus métier
Le contexte d'Application
Vous pouvez réaliser une recherche par priorité en appelantContexts.lookupInStatefulContexts()
. Malgré cela, si vous accédez a un composant par son nom depuis une page JSF, une recherche par ordre de priorité est faite.
Ni les spécifications servlet ni EJB ne définissent la moindre indications pour l’organisation des requêtes originaire du même client. Le container de servlet simplement laisse tous les threards s’exécuter de manière concurrente et laisse la sureté des threads se renforcer dans le code applicatif. Le container EJB autorise les composants sans état à être accédés de manière concurrente et déclenche des exceptions si de multiples threads accèdent à un bean session avec état.
Cette fonctionnalité devrait être ok dans les applications web au style ancien qui sont basées autour de requêtes bien dimensionnées et synchrones. Mais les applications modernes qui font une utilisation lourde de nombreuses requêtes bien dimensionnées et asynchrones (AJAX), de manière concurrente est un état de fait doivent être supporté par le modèle de programmation. Seam fourni une couche de gestion concurrente dans son modèle de contexte.
La session Seam et les contextes d’application sont multi threadées. Seam va nous autoriser des requêtes concurrentes dans un contexte pouvant être exécuté en concurrence. Les contextes page et d'évènements sont par nature un simple thread. Le contexte de processus métier est strictement parlant multi threadé, mais en pratique la concurrence est suffisamment rare pour que ce fait puisse être assez peu controlé la plus part du temps. Finalement, Seam renforce le modèle d'un seul thread par conversation par processus pour le contexte de conversation en sérialisant les requêtes concurrentes dans un même contexte de conversation d’exécution longue.
Depuis que le contexte de session est multi-threadé et souvent il contient un état volatile, les composants de l’étendue session sont toujours protégés par Seam d’accès concurrents aussi longtemps que les intercepteurs de Seam ne sont pas désactivés par ce composant. Si les intercepteurs sont désactivés, alors tout accès sécurisé au thread doit être implémenté par le concurent lui-même. Seam sérialise les requêtes dans les beans de session l’étendue de session et dans les JavaBeans par défaut (et détecte et brise toute étreinte mortele qui apparait). Ceci n’est pas la fonctionnalité par défaut pour les composants de l’étendue application, car les composants de l’étendue application ne conservent pas habituellement un état volatile et tout cela à cause de la synchronisation au niveau global qui est extrèmement couteuse. Malgré tout, vous pouvez forcer le modèle de threads en sérialisation sur n’imoporte quel bean session ou composant JavaBean en ajoutant l’annotation @Synchronized.
Le modèle concurrent signifie que les clients AJAX peuvent de manière sure utiliser la session volatile et un état conversationnel, sans le besoin d’un travail particulier de la part du développeur.
Les composants de Seam sont ds POJOs (Plain old java Objects). En particulier, s'ils sont des JavaBeans ou bean entreprise EJB3.0. Tant que Sean ne requière pas que ces composants soient des EJBs et ainsi qu’il puisse même être utilisés sans un container compatible EJB3, Seam a été conçu avec EJB3.0 comme état d’esprit et il inclus une intégration profonde avec EJB3.0. Seam supporte les types de composants suivants.
Beans de session sans état EJB 3.0
Beans de session avec état EJB 3.0
Beans entité EJB 3.0 (autrement dit, des classes d'entité JPA)
JavaBeans
Les beans conducteur de message EJB 3.0
Les beans de Spring (see Chapitre 27, Spring Framework integration)
Les composants de bean session sans état ne sont pas capables de retenir un état au travers de multiples invocations. Ainsi, ils travaillent habituellement en opérant au dessus de l’état d’autres composants dans les différents contextes de Seam. Ils peuvent être utilisés comme écouteur d’action JSF mais ne peuvent fournir une propriété à des composants JSF pour l’affichage.
Les beans session sans états existent toujours dans un contexte sans état.
Les beans de session sans état peuvent être accédés de manière concurente comme une nouvelle instance qui est utilisé pour chaque requête. Assigner l'instance à une requête est de la responsabilité du containeur EJB3 (les instances normallement seront alloués depuis un groupement réutilisable ce qui signifie que vous pourriez trouver des variables d'instance contenant des données d'un utilisation précédente du bean).
Les beans session sans états sont les moins intéressants des types de composant Seam.
Les beans session sans états peuvent être instanciés en utilisant Component.getInstance()
ou @In(create=true)
. Ils ne devraient pas être instanciés via l'observateur JNDI ou par l'opérateur new
.
Les composants beans de session avec état sont capables de retenir un état pas seulement au travers de multiples invocations du bean mais aussi au travers de multiples requêtes. L’état de l’application ne doit pas s’attarder dans la base de données pour être éventuellement retenu par des beans de sessions avec état. Ceci est une différence principale entre Seam et d’autres serveurs d’application web. Plutôt que de coller de l’information à propos de la conversation courante directement dans le HttpSession
, vous devriez conserver ces variables d’instance d’un bean session avec état qui va être relié au contexte de conversation. Ceci autorise Seam à gérer le cycle de vie de cet état pour vous et vous assure qu’il n’y aura pas de collision entre les états dans différentes conversations concurrentes.
Les beans de session avec état sont souvent utilisés comme écouteur d’action JSF, et comme beans de soutient qui fournissent des propriétés aux composants JSF pour l’affichage ou la soumissions de formulaire.
Par défaut, les beans de session avec état sont reliés au contexte de conversation. Ils ne peuvent jamais reliés aux contextes de page ou sans état.
Les requêtes concurrentes pour les sessions avec état dans l’étendue de session sont toujours sérialisées par Seam si les intercepteurs de Seam ne sont pas désactivés par le bean.
Les beans session avec états peuvent être instanciés en utilisant Component.getInstance()
ou @In(create=true)
. Ils ne devraient pas être instanciés via l'observateur JNDI ou par l'opérateur new
.
Les beans entité peuvent être reliés à une variable de contexte et fonctionne comme un composant de Seam. Avec des entitées ayant un identité persistante en plus de leur identité contextuelle, les instance d’entité sont habituellement reliées explicitement par le code Java, plutôt qu’être instanciées implicitement par Seam.
Les composants bean entité ne supportent pas la bijection ou la démarcation du contexte. Aucune invocation d’un bean entité ne déclenchera la validation.
Les beans entité ne sont habituellement pas utilisés comme écouteur d’action JSF, mais ont aussi la fonction d’un bean de soutient qui fourni des propriétés aux composants JSF pour l’affichage et la soumission de formulaire. En particulier, il est commun d’utiliser une entité comme un bean de soutient, avec un écouteur d’action de bean de session sans état pour implémenter les fonctionnalités de type création/mise à jour/effacement.
Par défaut, les beans entités sont reliés au contexte de conversation. Ils ne devraient jamais être reliés au contexte sans état.
Notez que dans un environement en cluster il est parfois moins efficace de relier un bean entité directement à une conversation ou à une variable de contexte de Seam d'étendue de session que de maintenir une reference au bean entité dans une bean de session avec état. C'est pour cette raison que toutes les applications Seam ne définissent pas des beans entité a être des composants Seam.
Les composant beans entité de Seam peuvent être instanciés en utilisant Component.getInstance()
ou @In(create=true)
ou directement en utilisant l'opérateur new
.
Les Javabeans devraient être utilisé tout comme un bean de session avec ou sans état. Magrè tout, ils ne fournissent pas les fonctionnalités de bean de session (démarcation de transaction déclarative, sécurité déclarative, réplication d’état en cluster efficace, peristance EJB3.0, méthode hors-delais,etc).
Dans un chapitre suivant, nous vous montrerons comment utiliser Seam et Hibernate sans un container EJB. Dans ce cas d’utilisation, les composants sont des JavaBeans au lieu de beans de session. Notez, malgré cela, dans beaucoup de serveurs d’application il est parfois moins efficace de rassembler les conversations ou les composants JavaBeans de Seam d’étendue session que de rassembler les composants bean de session avec état.
Par défaut, les JavaBeans sont reliés au contexte d’évènement.
Les requêtes concurrentes dans les Javabeans d’étendue session sont toujours sérialisées par Seam.
Les composant JavaBean de Seam peuvent être instanciés en utilisant Component.getInstance()
ou @In(create=true)
. Ils ne devraient jamais être instancié en utilisant l'opérateur new
.
Les beans conducteur-de-message peuvent fonctionner comme un composant Seam. Malgrè cela, les beans conducteur-de-message sont appeler un peu différemment des autres composants de Seam- au lieu de les invoquer via une variable de contexte, ils écoutent les messages envoyés vers une file d’attente JMS ou un topic.
Les beans conducteur-de-message peuvent ne pas être reliés à un contexte de Seam. Ils n’ont pas non plus d’accès à la session ou l’état de conversation de leur "appelant". Magrè cela, ils peuvent supporter la bijection et quelques autres fonctionnalités de Seam.
Les beans conducteur-de-message ne sont jamais instanciés par l'application. Ils sont instanciés par le containeur EJB quand un message est reçu.
Pour réussir à faire sont tour de magie (bijection, démarcation de contexte, validation, etc.), Seam doit intercepter les invocations de composant. Pour les JavaBeans, Seam est un contrôleur total de l’instanciation du composant, et aucune configuration spéciale n’est requise. Pour les beans entité, l’interception n’est pas requise à moins d'une bijection et la démarcation de contexte ne sont pas défini. Pour les beans session, nous devons enregistrer un intercepteur EJB pour le composant bean de session. Nous pourrions utiliser une annotation, comme ci-dessous :
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login {
...
}
Mais la meilleure façon de faire est de définir un intercepteur dans 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
>
Tous les composants de Seam au besoin d’un nom. Nous pouvons assigner un nom à un composant en utilisant l’annotation @Name
:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
...
}
Ce nom est un nom de composant seam et n’est pas lié a un tout autre nom défini par la spécification EJB. Malgré tout, les noms de composant Seam fonctionnent tous comme les noms des beans de gestion JSF et vous pouvez penser que les deux concepts sont identiques.
@Name
n'est pas la seule façon de définir un nom de composant, mais nous avons toujours besoin de spécifier ce nom quelque part. Si nous ne le faisons pas, alors aucune autre annotation de Seam ne va fonctionner.
A chaque fois que Seam instancie un composant, il relie cette nouvelle instance à une variable dans l'étendue configuré pour ce composant qui correspondant au nom du composant. Cette fonctionnalité est identique à comment JSF fait travailler les beans qu'il gère, exception que Seam vous permet de configurer cette relation en utilisant des annotation au lieu de XMM. Par exemple, le connecté courant dans User
peut être relié à une variable de contexte de session currentUser
tant que le User
dont il est sujet à quelques fonctionnalités administratives qui peuvent être reliées à la variable de contexte de conversation user. Soyez prudent, pensez-y, parce qu'au traver d'une affectation programmée, il est possible de surcharger une variable de contexte qui a une référence sur un composant de Seam, avec de potentiel problèmes de confusion.
Pour de plus grande applications, et pour les composants de Seam livrés, les noms qualifiés sont souvent utilisés.
@Name("com.jboss.myapp.loginAction")
@Stateless
public class LoginAction implements Login {
...
}
Nous devrions utiliser le nom de composant qualifié à la fois dans le code Java et dans le langage d’expression de JSF:
<h:commandButton type="submit" value="Login"
action="#{com.jboss.myapp.loginAction.login}"/>
Comme c’est perturbant, Seam fourni aussi une manière pour renommer un nom qualifié en nom simple. Ajouter une ligne comme celle-ci dans le fichier components.xml:
<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>
Tous les composants Seam livrés ont un nom qualifié, mais la plus part d’entre eux sont renommés vers un nom simple grâce à la fonctionnalité d'importation de l'espace de nommage de Seam. Le fichier components.xml
inclus dans le JAR de Seam définie les espaces de nommage suivant.
<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>
Pour essayer de résoudre un nom non-qualifié, Seam va vérifier chacun de ces espace de nommage, dans l'odre. Vous pouvez inclure des espaces de nommages additionnels dans votre fichier components.xml
de votre application pour des espaces de nommages spécifiques à votre application.
Nous pouvons surchargé létendue par défaut (le contexte) d’un composant en utilisation l’annotation @Scope
.Ceci nous permets de définir quel contexte une instance de composant est relié à, quand il est instancié par Seam.
@Name("user")
@Entity
@Scope(SESSION)
public class User {
...
}
org.jboss.seam.ScopeType
defini une énumération des étendues possibles.
Des classes de composants Seam peuvent être utilisées avec plus d’un rôle dans le système. Par exemple, nous avons souvent une classe User
qui est habituellement utilisée comme un composant d’étendue de session représentant l’utilisateur courant mais elle est aussi utilisée dans les écrans de l’administration de l’utilisateur comme un composant d’étendue de conversation. L’annotation @Role
nous permet de définir un rôle avec un nom additionnel, avec une étendue différente — il nous laisse relier la même classe de composant dans des variables de contextes différentes. (Toute iinstancei de composant Seam peut être reliée à de multiples variables de contexte, mais cela nous laisse le faire au niveau classe et d'avoir l’avantage d’une auto instanciation.)
@Name("user")
@Entity
@Scope(CONVERSATION)
@Role(name="currentUser", scope=SESSION)
public class User {
...
}
L'annotation @Roles
nous permet de spécifier autant de rôles additionnels que nous voulons.
@Name("user")
@Entity
@Scope(CONVERSATION)
@Roles({@Role(name="currentUser", scope=SESSION),
@Role(name="tempUser", scope=EVENT)})
public class User {
...
}
Tout comme beaucoup de bon serveur d’application, Seam mange sa propre nourriture et il implémente un série d’intercepteurs livrés (voir ci-dessous) et de composants de Seam. Ceci rend plus facile pour les applications d'interagir avec les composants livrés à l’exécution ou même de personnaliser les fonctionnalités de base de Seam en remplaçant les composants livrés avec des implémentations personnalisées. Les composants livrés sont défini dans l’espace de nommage de org.jboss.seam.core
et le paquet Java du même nom.
Les composants livrés peuvent être injectés, tout comme des composants Seam, mais ils fournissent aussi des méthodes instance()
pratiques statiques:
FacesMessages.instance().add("Welcome back, #{user.name}!");
L'injection de dépendance ou l'inversion de contrôle est maintenant un concept familier pour la plus part des développeurs Java. L’injection de dépendance permet à un composant d'obtenir une référence sur un autre composant en passant par le containeurs qui "injecte" l'autre composant par une méthode assesseur ou par une variable d'instance. Dans toutes les implémentations d’injection de dépendances que nous avons vu, l’injection intervient quand le composant est construit, et la référence ne change pas postérieurement pour le cycle de vie de l’instance du composant. Pour les composants sans état, ceci est raisonnable. Du point de vue d’un client, toutes les instances d’un composant sans état particulier sont interchangeables. Dans un autre côté, Seam accentue l’utilisation de composants avec état. Donc l’injection de dépendance traditionnelle n’est plus une construction vraiment utilisable. Seam introduit la notion de bijection comme une généralisation de l’injection. A la différence de l’injection, la bijection est :
contextuelle - la bijection est utilisé pour assembler les composants avec état depuis plusieurs contextes différents (un composant d’un contexte "évasé" peut même contenir une référence sur un composant d’un contexte "étroit")
bidirectionelle - les valeurs sont injectées depuis les variables de contexte dans des attributs du composant qui a été invoqués et aussi extradé depuis les attributs du composant en retour vers le contexte, autorisant le composant à être invoqué pour manipuler les valeurs des variables contextuelles simplement en définissant ces propres variables d’instances
dynamique - avec une valeur des variables contextuelles changeante au cours du temps et avec des composants Seam qui sont avec état, la bijection a lieu à chaque fois qu’un composant est invoqué
Par essence, la bijection vous permet de renommer une variables de contexte dans une variables d’instance de composant en spécifiant que la valeur de la variable d’instance est injectée, extradée ou les deux. Bien sûr, nous utilisons des annotation pour activer la bijection.
L'annotation @In
indique que la valeur devrait être injectée, aussi dans une variable d’instance.
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In User user;
...
}
ou dans une méthode assesseur :
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@In
public void setUser(User user) {
this.user=user;
}
...
}
Par défaut, Seam va donner une priorité de recherche dans tous les contextes en utilisant le nom de la propriété ou de la variable d’instance qui a été injectée. Vous devriez vouloir spécifier le nom de la variable de contexte explicitement, en utilisant, par exemple, @In("currentUser")
.
Si vous voulez que Seam puisse créer une instance du composant quand il n’y a pas d’instance de composant existante reliée à une variable de contexte nommée, vous pouvez spécifier @In(create=true)
. Si la valeur est optionnel (elle peut être null) spécifiez @In(required=false)
.
Pour quelques composants, il peut être répétitif d’avoir à spécifier @In(create=true)
partout où ils sont utilisés . Dans ce genre de cas, vous pouvez annoter le composant @AutoCreate
, et alors il va toujours être créé, n’important quand, même sans l’utilisation explicite de create=true
.
Vous pouvez même injecter la valeur d’une expression :
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In("#{user.username}") String username;
...
}
Les valeurs injectées sont désalouées (autrement dit, définie à null
) immédiatement après la réalisation de la méthode et sont extraction.
(Il y a beaucoup plus d'information sur le cicle de vie d'un composant et l'injection dans le chapitre suivant).
L'annotation @Out
indique qu'un attribue devrait être extradé, tout comme une variable d'instance:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@Out User user;
...
}
ou comme une méthode assesseur:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@Out
public User getUser() {
return user;
}
...
}
Un attribut peut être à la fois injecté et extradé:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In @Out User user;
...
}
ou:
@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;
}
...
}
Le bean de session et les composants Seam de bean entité supportent tous les rappels du cycle de vie EJB3.0 usuel (@PostConstruct
, @PreDestroy
, etc). Mais Seam étend tous ces rappels aux composant JavaBean. Cependant tant que ces annotation ne sont pas disponible dans un environement J2EE, Seam défini deux composants additionnel pour le rappel dans le cycle de vie, équivalent à @PostConstruct
et à @PreDestroy
.
La méthode @Create
est appelée après que Seam instancie un composant. Les composants ne peuvent définir seulement qu'une seule méthode @Create
.
La méthode @Destroy
est appelé quand le contexte qui lie le composant de Seam se termine. Les composants ne peuvent définir qu'une seule méthode @Destroy
.
De plus, les composants bean avec état doivent définir une méthode sans aucun paramètre annotée @Remove
. Cette méthode est appelé par Seam quand le contexte se termine.
Finalement, une annotation liée est l’annotation @Startup
qui peut être appliquée à toutes l’application ou un composant d’étendue session. L’annotation @Startup
indique à Seam d’instancier le composant immédiatement, quand le contexte commence, au lieu d’attendre avant son premier référencement par un client. Il est possible de controler l’ordre d’instanciation du démarrage des composants en spécifiant @Startup(depends={....})
.
L'annotation @Install
vous permet de contrôler l’installation conditionnelle du composant qui sont requis dans certains scénarios de déploiement et pas dans d’autres. C'est utile si :
Vous voulez singer quelques composants d’infrastructure dans des tests.
Vous voulez modifier l’implémentation d’un composant dans certains scénarios de déploiement.
Vous voulez installer quelques composants seulement si leurs dépendances sont disponibles (utile pour les auteurs des serveur d’applications).
@Install
fonctionne en vous laissant spécifier la précédence et les dépendances.
La précédence d’un composant est un numéro que Seam utilise pour décider quel composant est à installer quand il a plusieurs classes avec le même nom de composant dans le classpath. Seam va choisir le composant avec la précédence la plus haute. Il y a quelques valeurs de précédences prédéfinie (dans l’ordre ascendant) :
BUILT_IN
— les composants de précédence la plus faible sont les composants livrés avec Seam.
FRAMEWORK
— les composants définis par un serveur d’application tierces peuvent surcharger les composants livrés, mais ils sont surchargés par les composants d’application.
APPLICATION
— la précédence par défaut. C'est la valeurappropriée pour la plus part des composants d’application.
DEPLOYMENT
— pour les composants d’application qui ont un déploiement spécifique.
MOCK
— pour les objets singeant qui sont utilisés en test.
Supposez que nous avons un composant appelé messageSender
qui parle à la queue de JMS.
@Name("messageSender")
public class MessageSender {
public void sendMessage() {
//do something with JMS
}
}
Si dans vos tests unitaires, nous n’avons pas de queue de JMS disponible, mais que nous aimerions simuler cette méthode. Nous allons créer un composant le singeant et qui existe dans le classpath quand les tests unitaires sont exécutés mais il n’est jamais déployé avec l’application:
@Name("messageSender")
@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
public void sendMessage() {
//do nothing!
}
}
La precedence
aide Seam à décider quel version à utiliser quand il trouve deux composants dans la classpath.
C’est bien si vous étiez capable de contrôler exactement quelles classes sont dans le classpath. Mais si j’ai écrit un serveur d’application réutilisable avec beaucoup de dépendances, je ne veux pas à avoir à briser ce serveur d’applications au travers de nombreux jars. Je veux d’être capable de décider quel composants sont à installer selon quel autres composants sont déjà installés et selon quelles classes sont disponibles dans le classpath. L’annotation @Install
contrôle aussi cette fonctionnalité. Seam utilise ce mécanisme interne pour activer l’installation conditionnelle de beaucoup des composants livrés. Malgré tout, vous n’aurez probablement pas besoin de l’utiliser dans votre application.
Qui n’en n’a pas assez de voir du code aussi touffu que celui-ci ?
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);
}
Il est difficile d’imaginer comment le code pour un simple message de log peut devenir beaucoup verbeux. Il y a beaucoup plus de lignes de code dédié à l'affichage des traces plus que de la réelle logique métier! Je reste totalement abasourdie que la communauté Java n’a pas encore trouvé mieux depuis 10 ans.
Seam fourni un API d'affichage des traces qui simplifie significativement le code :
@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);
}
Il n’est pas utile si vous déclarez la variable log
statique ou non— elle va fonctionner dans tous les cas, à l’exception des composants bean entité qui demande à la variable de log
d'être statique.
Notez que nous n’avons pas besoin d’être touffu si ( log.isDebugEnabled() )
conserver, depuis la concaténation des chaines de caractères apparaissent dans la méthode debug()
.Notez aussi que nous n’avons habituellement pas besoin de spécifier la catégorie de log explicitement, car Seam connaît quel composant au sein du quel il va injecter le Log
.
Si User
et Product
sont des composants Seam disponible dans les contextes courant, il n’y a pas mieux :
@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 logue auto-magiquement choisi s’il faut envoyer la sortie vers log4j ou vers le logging JDK. Si log4j est dans le classpath, Seam va l’utiliser. Si ce n’est pas le cas, Seam va utiliser le logging JDK.
Plusieurs serveurs d’applications fonctionnent comme d’incroyable implémentation brissant le clustering HttpSession
quand les modifications d’état d’objet mutables relié à une session sont seulement répliqués quand l’application appelle setAttribute()
explicitement. Ceci est la source de bugs qui ne peuvent pas effectivement être testé pendant le temps de développement, jusqu’à qu’ils se manifestent quand les erreurs surviennent. En outre, le message actuel répliqué contient le graphe d’objet intégralement sérialisé relié à l’attribut de la session, ce qui est inefficace.
Bien sur, les beans de session avec état EJB peuvent réaliser automatiquement la sale vérification et la réplication des états mutés et un containeur EJB sophistiqué peut introduire des optimisations comme la réplication au niveau attribut. Malheureusement, tous les utilisateurs de Seam n’ont la bonne surprise de travailler dans un environnement qui supporte EJB3.0. Donc, pour la session et le JavaBean d’étendue conversationnelle et les composants bean entité, Seam propose une couche additionnelle pour le gestionnaire d’état sécurisé pour le cluster au dessus du clustering de session du container web.
Pour la session ou des composants de JavaBeans d’étendue conversationnel, Seam automatiquement force la réplication qui survient en appelantsetAttribute()
une fois sur chaque requète quand le composant est invoqué par l'application.Bien sur, cette stratégie est inéficace pour les composants principalement en lecture seul. Vous pouvez controler cette fonctionnalité en implémentant l'interface org.jboss.seam.core.Mutable
et écrire votre propre logique de sale-vérification dans votre composant. Par exemple :
@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;
}
...
}
Ou, vous pouvez utiliser l’annotation @ReadOnly pour obtenir un effet similaire :
@Name("account")
public class Account
{
private BigDecimal balance;
public void setBalance(BigDecimal balance)
{
this.balance = balance;
}
@ReadOnly
public BigDecimal getBalance()
{
return balance;
}
...
}
Pour la session ou pour des composants bean entité d’étendue conversation, Seam automatiquement forces la réplication qui intervient en appelant setAttribute()
une fois par requête, à moins que l'entité (d'étendue conversationnelle) ne soit actuellement associée avec le contexte de persistance gérée par Seam, dans ce cas aucune réplication n'est requise. Cette stratégie n'est pas nécéssairement efficace, donc une session ou des beans entité d'édendue conversationnelle devraient l’utiliser avec prudence. Vous devez toujours écrire un bean session avec état ou un composant JavaBean pour "gérer" l’instance du bean entité. Par exemple,
@Stateful
@Name("account")
public class AccountManager extends AbstractMutable
{
private Account account; // an entity bean
@Unwrap
public Account getAccount()
{
return account;
}
...
}
Notez que la classe EntityHome
dans le Seam Application Framework fourni un bel exemple de gestion d’une instance bean entité utilisant un composant Seam.
Nous avons souvent besoin de travailler avec des objets qui ne sont pas des composants Seam. Mais nous continuons à vouloir être capable de les injecter dans notre composants en utilisant @In
et de les utiliser en tant que valeur et méthode reliées aux expressions, etc. De temps en temps, nous avons même besoin de les attacher dans le cycle de vie du contexte de Seam (@Destroy
, par exemple). Donc les contextes de Seam peuvent obtenir des objets qui ne sont pas des composants Seam et Seam fourni quelques fonctionnalités sympa qui rendent plus facile le travail avec les objets non-composants reliés aux contextes.
Le modèle de conception fabrique de composant autorise un composant Seam à agir comme l’instanciateur pour un objet non-composant. Une méthode fabrique va être appelée quand une variable du contexte est référencée mais n’a pas de valeur reliée à elle. Nous définissons des méthodes fabrique en utilisant l’annotation @Factory
. La méthode fabrique relie une valeur à une variable de contexte et détermine l’étendue de la valeur liée. Il y a deux types de méthode fabrique. La première méthode retourne une valeur, qui est relié au contexte par Seam :
@Factory(scope=CONVERSATION)
public List<Customer
> getCustomerList() {
return ... ;
}
Le second style est une méthode de type void qui relie cette valeur à la variable de contexte elle-même :
@DataModel List<Customer
> customerList;
@Factory("customerList")
public void initCustomerList() {
customerList = ... ;
}
Dans les deux cas, la méthode fabrique est appelée quand nous référençons la variable de contexte customerList
et cette valeur est null, et alors n’a absolument pas à intervenir dans le cycle de vie de la valeur. Et même un patron de conception plus puisssant est le pattron de conception gestionnaire de composant. Dans ce cas, nous avons un composant Seam qui est relié à une variable de contexte qui gère la valeur de la variable de contexte restant invisible aux clients.
Un composant gestionnaire est n’importe quel composant avec une méthode @Unwrap
. Cette méthode retourne la valeur qui va être visible pour les clients, et elle est appeléeà chaque fois qu'une variable de contexte est référencée.
@Name("customerList")
@Scope(CONVERSATION)
public class CustomerListManager
{
...
@Unwrap
public List<Customer
> getCustomerList() {
return ... ;
}
}
Le patron de conception gestionnaire est particulièrement utile si nous avons un objet où nous avons besoin de contrôler le cycle de vie du composant. Par exemple, si vous avez des objets très lourds qui ont besoin d’opération de nettoyage quand le contexte s’arrête, vous pouvez @Unwrap
l'objet et réaliser un nettoyage dans la méthode @Destroy
du gestionnaire.
@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();
}
...
}
Ici le composant gestionnaire observe tous les évènement qui modifie l'objet sous-jacent. Le composant gère ces actions lui-même, et parce que l'objet est empaqueté donc à chaque accès, une vue cohérante est fournie.