Vous pouvez créer une classe d'implémentation de bean de session avec état
(Stateful) conformément à un procédé introduit dans la spécification Enterprise JavaBeans™ (EJB) 1.0, qui
a été considérablement simplifié par la spécification EJB 3.0. Un bean avec état est un type de bean de session prévu pour être utilisé
par un seul client tout au long de son cycle de vie et qui maintient un état conversationnel
avec le client qui l'appelle.
Avant de commencer
Assurez-vous que vous comprenez les règles d'héritage pour chaque annotation
implémentée. Par exemple, l'annotation @TransactionManagement est codée
uniquement dans la classe du bean de session avec état. Vous ne pouvez pas utiliser cette annotation
dans la classe étendue par la classe du bean, encore moins dans une classe située plus haut dans
sa hiérarchie d'héritage.
Pourquoi et quand exécuter cette tâche
Les beans de session avec état peuvent avoir les
vues suivantes : vue locale sans interface (nouveauté propre à EJB 3.1), métier locale, métier distante,
locale propre à EJB 2.1 et distante propre à EJB 2.1. Un exemple est le panier d'achats auquel le client ajoute des articles
lors d'une session d'achat en ligne.
L'exemple suivant montre un bean de session avec état basique :
package com.ibm.example;
public interface ShoppingCart {
void addToCart (Object o);
Collection getContents();
}
package com.ibm.example;
@Stateful
public class ShoppingCartBean implements ShoppingCart {
private ArrayList contents = new ArrayList();
public void addToCart (Object o) {
contents.add(o);
}
public Collection getContents() {
return contents;
}
}
Comme pour les autres types de bean enterprise, vous pouvez déclarer les métadonnées de votre
bean de session avec état dans le descripteur de déploiement plutôt qu'en utilisant des annotations.
Par exemple :
<?xml version="1.0"?>
<ejb-jar
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
version="3.1">
<enterprise-beans>
<ejb-name>ShoppingCartBean</ejb-name>
<business-local>com.ibm.example.ShoppingCart</business-local>
<ejb-class>com.ibm.example.ShoppingCartBean</ejb-class>
<session-type>Stateful</session-type>
</enterprise-beans>
</ejb-jar>
- Codez les méthodes d'initialisation et de destruction en comprenant bien
qu'elles s'exécutent dans un contexte de sécurité non spécifié et dans un
contexte transactionnel également non spécifié. Pendant l'initialisation, l'instance est créée, une injection de dépendances
a lieu et les intercepteurs pour rappels d'événement de cycle de vie PostConstruct
sont appelés. Les intercepteurs pour rappels d'événement de cycle de vie
PreDestroy sont invoqués pour un bean de session avec état lorsque la méthode remove est
appelée. Pensez également que ces intercepteurs ne sont pas appelés si le
bean expire (timeout) alors qu'il est à l'état passif ou si une exception inattendue
se produit durant une invocation de méthode sur le bean et que cette exception entraîne
la mise à l'écart du bean par le conteneur.
- Utilisez les méthodes PrePassivate et PostActivate si le bean de session peut
contenir un état qui n'est pas sérialisable. Le conteneur peut passiver
un bean de session avec état à tout moment si ce bean n'est enrôlé dans aucune transaction et
s'il n'est pas en train d'exécuter une demande de méthode. L'instance du bean est mise à l'état passif par sérialisation de toutes ses
données d'état. Si l'une quelconque de ces données d'état ne peut pas être sérialisée,
le conteneur met à l'écart (supprime) l'instance du bean de session.
- Pensez à implémenter l'interface optionnelle javax.ejb.SessionSynchronization
si les données d'état du bean de session doivent être réinitialisées après
l'annulation d'une transaction. Les données d'état d'un bean de session avec état
ne sont pas transactionnelles et ne sont pas ramenées à leur état initial
consécutivement à une annulation de transaction. En implémentant
la méthode afterCompletion de l'interface javax.ejb.SessionSynchronization,
vous donnez à l'instance du bean de session la possibilité de rétablir son état initial
ou de retrouver un état cohérent.
- Utilisez la notation @AccessTimeout pour interdire les demandes de client concurrentes
ou limiter la durée pendant laquelle une méthode peut attendre qu'un verrou d'instance
soit octroyé. Par défaut, le conteneur autorise les demandes de client concurrentes,
mais il sérialise tous les appels de méthode et les rappels invoqués par le conteneur
pour empêcher que plusieurs unités d'exécution n'accèdent en même temps à l'instance
de bean de session avec état. Ce comportement s'apparente à l'utilisation de la concurrence d'accès gérée par le conteneur
avec application de verrous d'écriture pour les beans de session singleton. Cependant, contrairement aux
beans singleton, un bean de session avec état ne peut pas être configuré pour gérer lui-même
la concurrence d'accès à ses méthodes, et le type de verrou ne peut pas
être changé. Seule la valeur du délai d'accès (access-timeout) peut être
changée pour un bean de session avec état. L'exemple de code suivant montre un bean de session avec état, configuré
avec une valeur de délai d'accès qui interdit les demandes de client concurrentes :
package com.ibm.example;
@Stateful
public class ShoppingCartBean implements ShoppingCart {
private ArrayList contents = new ArrayList();
@AccessTimeout( value=0 )
public void addToCart (Object o) {
contents.add(o);
}
public Collection getContents() {
return contents;
}
}
package com.ibm.example;
@Stateful
public class ShoppingCartBean implements ShoppingCart {
private ArrayList contents = new ArrayList();
@AccessTimeout( value=0 )
public void addToCart (Object o) {
contents.add(o);
}
public Collection getContents() {
return contents;
}
}
Si aucune annotation n'est fournie, le comportement par défaut consiste à attendre
jusqu'à ce qu'un verrou soit obtenu. Il n'y a pas de limite de temps et un client peut donc attendre indéfiniment qu'un
verrou lui soit octroyé. Comme rien n'est codé au niveau de la classe, il n'y a pas de limite de temps d'attente
d'un verrou pour l'accès à l'une quelconque des méthodes de la classe. Si, au contraire, une annotation @AccessTimeout
est utilisée et que le conteneur ne peut accorder un verrou dans le temps imparti,
une exception javax.ejb.ConcurrentAccessTimeoutException
est envoyée au client. L'annotation @AccessTimeout s'applique uniquement aux méthodes déclarées dans la classe où figure
cette annotation. Une classe particulière n'hérite jamais des métadonnées @AccessTimeout d'une
classe située plus haut dans son arbre d'héritage.
Un délai d'accès (access-timeout) valant -1 indique que
les appels de méthode concurrents bloquent l'accès à l'instance de bean indéfiniment
(il s'agit du comportement par défaut). Un délai d'accès valant 0 indique que
les appels de méthode concurrents ne sont pas autorisés. L'exception javax.ejb.ConcurrentAccessException est alors émise si
un cas de concurrence est détecté. Toute valeur positive indique la durée d'attente possible.
Avant Java™ EE
6, le seule comportement de concurrence possible pour un bean de session avec
état était un délai d'accès de -1, soit aucune concurrence. Comme la spécification Java EE 6 a changé le comportement
par défaut, une propriété système permettant, au besoin, de rétablir l'ancien comportement,
a été introduite dans l'implémentation EJB 3.1 de notre produit. Consultez la description des propriétés système du conteneur EJB pour
plus de détails sur cette propriété, qui a pour nom
com.ibm.websphere.ejbcontainer.EE5Compatibility.
Vous pouvez également définir l'équivalent de l'annotation @AccessTimeout
dans le descripteur de déploiement XML. Dans ce cas, c'est ce dernier qui prévaut (si
l'annotation @AccessTimeout figure à l'endroit correspondant dans le code, ses métadonnées sont ignorées).
L'exemple suivant utilise le descripteur de déploiement XML pour définir les mêmes métadonnées que dans l'exemple précédent.
<session>
<ejb-name>ShoppingCartBean</ejb-name>
<concurrent-method>
<method>
<method-name>addToCart</method-name>
</method>
<access-timeout>
<timeout>0</timeout>
<unit>Milliseconds</unit>
</access-timeout>
</concurrent-method>
</session>
- Il est important de savoir que le codage XML de l'élément concurrent-method suit les trois styles décrits dans la
spécification EJB de composition XML pour les éléments de méthode container-transaction. Ces trois styles sont les
suivants : Le style 1 utilise le nom de méthode spécial * pour appliquer le délai
d'accès à toutes les méthodes métier du bean spécifié.
<!-- Exemple : Style 1 -->
<concurrent-method>
<method>
<method-name>*</method-name>
</method>
<access-timeout>
<timeout>2000</timeout>
<unit>Milliseconds</unit>
</access-timeout>
</concurrent-method>
Le style 2 permet de désigner une méthode métier particulière et de lui
associer une valeur de délai d'accès spécifique. Si le nom de méthode est surchargé, ce qui implique que plusieurs
méthodes portent le même nom, mais avec des signatures différentes, le délai d'accès spécifié est appliqué
à toutes les méthodes de ce nom.
<!-- Exemple : Style 2 -->
<concurrent-method>
<method>
<method-name>businessMethod</method-name>
</method>
<access-timeout>
<timeout>2000</timeout>
<unit>Milliseconds</unit>
</access-timeout>
</concurrent-method>
Le style 3 permet de désigner une méthode particulière, ayant à la fois le nom indiqué et la signature
correspondant aux paramètres listés. Le style 3 prévaut sur les styles 1 et 2.
<!-- Exemple : Style 3 -->
<concurrent-method>
<method>
<method-name>businessMethod</method-name>
<method-params>
<method-param>long</method-param>
<method-param>int</method-param>
</method-params>
</method>
<access-timeout>
<timeout>2000</timeout>
<unit>Milliseconds</unit>
</access-timeout>
</concurrent-method>
Si le XML du style 1 est utilisé pour définir un délai d'accès, toutes les annotations @AccessTimeout dans le bean sont ignorées,
car c'est le niveau 1 des éléments method qui est utilisé à la place.
- Veillez à bien comprendre les règles d'héritage des annotations que vous utilisez.
- Les beans de session avec état ne sont pas réentrants, ce qui signifie que leurs
méthodes se rappellent elles-mêmes. Si un appel réentrant est envoyé à
un bean de session avec état, une exception javax.ejb.ConcurrentAccessTimeoutException
est renvoyée au client appelant, quelle que soit la valeur du délai d'accès. Cette exception n'a pas pour conséquence de mettre à l'écart le bean de session avec
état ni de marquer la transaction pour annulation, sauf si l'exception n'est pas
traitée par l'appelant.