IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]


 

67. Les EJB 3

 

chapitre 6 7

 

Niveau : niveau 4 Supérieur 

 

Les EJB (Entreprise Java Bean) sont un des éléments très importants de la plate-forme Java EE pour le développement d'applications distribuées.

La plate-forme Java EE propose de mettre en oeuvre les couches métiers et persistance avec les EJB. Particulièrement intéressants dans des environnements fortement distribués, jusqu'à la version 3, leur mise en oeuvre est assez lourde sans l'utilisation d'outils tels que certains IDE ou XDoclet.

La version 3 des EJB vise donc à simplifier le développement et la mise en oeuvre des EJB qui sont fréquemment jugés trop complexes et trop lourds à mettre en oeuvre.

Cette nouvelle version majeure des EJB propose une simplification de leur développement tout en conservant une compatibilité avec sa précédente version. Elle apporte de très nombreuses fonctionnalités dans le but de simplifier la mise en oeuvre des EJB.

Cette simplification est rendue possible notamment par :

  • l'utilisation des annotations
  • la mise en oeuvre de valeurs par défaut qui répondent à la plupart des besoins (configuration par exception)
  • le descripteur de déploiement est facultatif
  • l'utilisation de POJO et de JPA pour les beans de type entity
  • l'injection de dépendances côté serveur mais aussi côté client (l'interface Home qui gérait le cycle de vie est abandonnée) qui remplace l'utilisation directe de JNDI
  • ...

Tous ces éléments délèguent une partie du travail du développeur au conteneur d'EJB.

Ce chapitre contient plusieurs sections :

 

67.1. L'historique des EJB

EJB 1.1 publié en décembre 1999, intégré dans J2EE 1.2 :

  • Session beans (stateless/stateful)
  • Entity Beans (CMP / BMP)
  • Interface Remote uniquement

EJB 2.0 publié en septembre 2001, intégré à J2EE 1.3 :

  • Message-Driven Beans
  • Entity 2.x reposant sur EJB QL
  • Interface Local pour améliorer les performances des appels dans la même JVM

EJB 2.1 publié en novembre 2003, intégré à J2EE 1.4 :

  • EJB Timer Service
  • EJB Web Service Endpoints via JAX-RPC
  • Amélioration du langage EJB QL

EJB 3.0, intégré à Java EE 5 :

  • utilisation de POJO et POJI, plus d'interface Home
  • utilisation des annotations, le descripteur de déploiement est optionnel
  • utilisation de JPA pour les beans de type entity

EJB 3.1, intégré à Java EE 6

 

67.2. Les nouveaux concepts et fonctionnalités utilisés

Dans les versions antérieures à la version 3.0 des EJB, le développeur était contraint de créer de nombreuses entités pour respecter l'API EJB (par exemple, l'implémentation d'interfaces engendrant la création de plusieurs méthodes ou le descripteur de déploiement), ce qui rendait l'écriture relativement lourde même avec l'assistance de certains IDE ou outils (XDoclet notamment). Dans la version 3.0, ceci est remplacé par l'utilisation d'annotations.

La mise en oeuvre de l'interface EJBHome n'est plus requise : un EJB de type session est maintenant une simple classe, qui peut implémenter une interface métier.

La seule annotation obligatoire dans un EJB est celle qui précise le type d'EJB (@javax.ejb.Stateless, @javax.ejb.Stateful ou @javax.ejb.MessageDriven).

Les annotations possèdent des valeurs par défaut qui répondent à une majorité de cas typiques d'utilisations. L'utilisation de ces annotations n'est alors requise que si les valeurs par défaut ne répondent pas au besoin. Ceci permet de réduire la quantité de code à écrire.

L'utilisation des annotations et de valeurs par défaut pour la plupart de ces dernières rend optionnelle la nécessité de créer un descripteur de déploiement sauf pour des besoins très particuliers.

Le conteneur obtient des informations sur la façon de mettre en oeuvre un EJB par trois moyens :

  • Des valeurs par défaut pour la plupart des annotations ce qui évite d'avoir à les déclarer explicitement dans le code
  • Les annotations utilisées dans le code
  • Le descripteur de déploiement

L'ordre d'utilisation par le conteneur est : le descripteur de déploiement, les annotations, les valeurs par défaut.

L'utilisation des annotations est plus simple à mettre en oeuvre mais le descripteur de déploiement permet de centraliser les informations.

La nouvelle API Java Persistence remplace la persistance assurée par le conteneur : cette API assure la persistance des données grâce à un mapping O/R reposant sur des POJO.

Le conteneur a la possibilité d'injecter des dépendances d'objets dont il assure la gestion.

Les intercepteurs permettent d'offrir des fonctionnalités proches de celles proposées par l'AOP : ceci permet de définir des traitements lors de l'invocation de méthodes des EJB ou d'invoquer des méthodes particulières liées au cycle de vie de l'EJB.

 

67.2.1. L'utilisation de POJO et POJI

Les classes et les interfaces des EJB 3.0 sont de simples POJO ou POJI : ceci simplifie le développement des EJB. Par exemple, l'interface Home n'est plus à déclarer.

Il est toujours possible d'implémenter les interfaces SessionBean, EntityBean et MessageDrivenBean mais le plus simple est d'utiliser les annotations définies : @Stateless, @Stateful, @Entity ou @MessageDriven

Exemple :
@Stateless 
public class HelloWorldBean {
  public String saluer(String nom) { 
    return "Bonjour "+nom;
  }
} 

Il est possible de définir une interface métier pour l'EJB ou de laisser générer cette interface lors du déploiement.

Dans le premier cas, il n'est plus nécessaire qu'elle implémente l'interface EJBObject ou EJBLocalObject mais il faut simplement utiliser les annotations définies : @Remote ou @Local.

Exemple :
@Remote
@Stateless 
public class HelloWorldBean {
  public String saluer(String nom)
  { 
    return "Bonjour "+nom;
  }
} 

Dans le second cas, ces annotations doivent être utilisées dans la classe d'implémentation pour permettre de déterminer l'interface générée.

Il est possible de définir une interface locale et/ou distante pour un même EJB.

Il n'est pas recommandé de laisser les interfaces être générées par le conteneur pour plusieurs raisons :

  • les interfaces générées exposent par défaut toutes les méthodes de l'EJB
  • l'interface est utilisée par le client pour invoquer l'EJB
  • le nom des interfaces générées utilise le nom de l'implémentation de l'EJB

 

67.2.2. L'utilisation des annotations

La spécification 3.0 des EJB fait un usage intensif des annotations. Celles-ci sont issues de la JSR 175 et intégrées dans Java SE 5.0 qui constitue la base de Java EE 5.

Les annotations sont des attributs ou métadonnées à l'image de celles proposées par XDoclet.

Avec les EJB 3.0, les annotations sont utilisées pour générer des entités et remplacer tout ou partie du descripteur de déploiement.

De nombreuses annotations permettent de simplifier le développement des EJB.

La nature de l'EJB est précisée par une des annotations @Stateless, @Stateful, @Entity et @MessageDriven selon le type d'EJB à définir.

Le type d'accès est précisé par deux annotations

  • @Remote : permet un accès à l'EJB depuis un client hors de la JVM
  • @Local : permet un accès à l'EJB depuis un client dans la même JVM que celle de l'EJB

Par défaut, l'interface d'appel est locale si aucune annotation n'est indiquée.

Dans le cas d'un accès distant, il est inutile que chaque méthode précise qu'elle peut lever une exception de type RemoteException mais elles peuvent déclarer la levée d'exceptions métiers.

Jusqu'à la version 2.1 des EJB, il était obligatoire d'implémenter plusieurs méthodes relatives à la gestion du cycle de vie de l'EJB notamment ejbActivate, ejbLoad, ejbPassivate, ejbRemove, ... pour chaque EJB même si ces méthodes ne contenaient aucun traitement.

Avec les EJB 3.0, l'implémentation de ces méthodes est remplacée par l'utilisation facultative d'annotations sur les méthodes concernées. La signature de ces méthodes doit être de la forme public void nomMethode()

Par exemple, pour que le conteneur exécute automatiquement une méthode avant de retirer l'instance du bean, il faut annoter la méthode avec l'annotation @Remove.

Plusieurs annotations permettent ainsi de définir des méthodes qui interviendront dans le cycle de vie de l'EJB.

Annotation

Rôle

@PostConstruct

la méthode est invoquée après que l'instance est créée et que les dépendances sont injectées

@PostActivate

la méthode est invoquée après que l'instance de l'EJB est désérialisée du disque. C'est l'équivalent de la méthode ejbActivate() des EJB 2.x

@Remove

la méthode est invoquée avant que l'EJB ne soit retiré du conteneur

@PreDestroy

la méthode est invoquée avant que l'instance de l'EJB ne soit supprimée

@PrePassivate

la méthode est invoquée avant que de l'instance de l'EJB ne soit sérialisée sur disque. C'est l'équivalent de la méthode ejbPassivate() des EJB 2.x


L'utilisation facultative de ces annotations remplace la définition obligatoire des méthodes de gestion du cycle de vie utilisées jusqu'à la version 2.1 des EJB.

Le descripteur de déploiement n'est plus obligatoire puisqu'il peut être remplacé par l'utilisation d'annotations dédiées directement dans les classes des EJB.

Chaque attribut de déploiement possède une valeur par défaut qu'il ne faut définir que si cette valeur ne répond pas au besoin.

Plusieurs annotations sont définies par les spécifications des EJB pour permettre de déclarer le type de bean, le type de l'interface, des références vers des ressources qui seront injectées, la gestion des transactions, la gestion de la sécurité, ...

Chaque vendeur peut définir en plus ses propres annotations dans l'implémentation de son serveur d'applications. Leur utilisation n'est cependant pas recommandée car elle rend l'application dépendante du serveur d'applications utilisé.

L'utilisation des annotations va simplifier le développement des EJB mais la gestion de la configuration pourra devenir plus complexe puisqu'elle n'est plus centralisée.

 

67.2.3. L'injection de dépendances

L'EJB déclare les ressources dont il a besoin à l'aide d'annotations. Le conteneur va injecter ces ressources lorsqu'il va instancier l'EJB donc avant l'appel aux méthodes liées au cycle de vie du bean ou aux méthodes métiers. Ceci impose que l'injection de ressources se fasse sur des objets gérés par le conteneur.

Ces ressources peuvent être de diverses natures : référence vers un autre EJB, contexte de sécurité, contexte de persistance, contexte de transaction, ...

Plusieurs annotations sont définies pour mettre en oeuvre l'injection de dépendances :

  • L'annotation @EJB permet d'injecter une ressource de type EJB.
  • L'annotation @Resource permet d'injecter une ressource qui est obtenue par JNDI (EntityManager, UserTransaction, SessionContext, ...)
  • ...

L'utilisation de l'injection de dépendances remplace l'utilisation implicite de JNDI.

L'injection peut aussi être définie dans le descripteur de déploiement.

 

67.2.4. La configuration par défaut

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

67.2.5. Les intercepteurs

Les intercepteurs sont une fonctionnalité avancée similaire à celle proposée par l'AOP : ils permettent d'intercepter l'invocation de méthodes pour exécuter des traitements.

Ils sont définis grâce à des annotations dédiées notamment @Interceptors et @AroundInvoke ou dans le descripteur de déploiement.

Leur utilisation peut être variée : traces, logs, gestion de la sécurité, ...

 

67.3. EJB 2.x vs EJB 3.0

Le développement d'EJB n'a jamais été facile et est même devenu plus complexe au fur et à mesure des nouvelles spécifications.

Avant la version 3.0 des EJB, les EJB étaient relativement complexes et lourds à mettre en oeuvre :

  • création de plusieurs interfaces et classes (deux interfaces et une classe au minimum)
  • implémentation de méthodes callback généralement inutiles
  • l'interface de l'EJB doit hériter de EJBObject ou de EJBLocalObject
  • chaque méthode de l'EJB doit déclarer pouvoir lever l'exception RemoteException
  • le descripteur de déploiement des EJB est complexe
  • les EJB entité de type CMP présentent plusieurs limitations : complexes à développer et à maintenir, de nombreux problèmes de performance, le langage EJBQL est limité
  • le support de la POO pour les EJB est très limité vis-à-vis de l'héritage
  • les EJB doivent être testés dans un conteneur ce qui les rend difficiles à déboguer
  • l'appel d'un EJB par un client nécessite obligatoirement une utilisation de JNDI

La version 3.0 des spécifications des EJB apporte une solution de simplification à tous les points précédemment cités.

  • Il n'est plus nécessaire de déclarer d'interfaces (pour des raisons de bonne pratique, la déclaration d'une interface métier contenant les méthodes proposées est cependant fortement recommandée)
  • Le descripteur de déploiement est optionnel sauf dans des cas particuliers
  • L'utilisation de POJO et POJI
  • L'injection de dépendances rend très facile l'obtention d'une instance d'une ressource gérée par le conteneur
  • ...

Les principales différences entre les EJB 2.x et EJB 3.0 sont donc :

  • les descripteurs de déploiement ne sont plus obligatoires grâce à l'utilisation d'annotations et de valeurs par défaut
  • Les EJB sont de simples POJO annotés : ils n'ont plus besoin d'implémenter une interface de l'API EJB. De fait, il n'est plus nécessaire de définir des méthodes liées au cycle de vie de l'EJB. Si ces méthodes sont nécessaires, il suffit d'utiliser des annotations dédiées sur une méthode.
  • Le type de l'interface de l'EJB est précisé avec l'annotation @Local ou @Remote
  • L'interface métier est une simple POJI
  • Les EJB de type Entity CMP et BMP sont remplacés par l'utilisation du modèle de persistance reposant sur l'API JPA

 

67.4. Les conventions de nommage

Il n'existe pas de règles imposées mais il est important de définir des conventions de nommage pour les différentes entités qui sont utilisées lors de la mise en oeuvre des EJB.

Exemple :

Nom du bean : CalculEJB

Nom de la classe métier : CalculBean

Interface locale : CalculLocal

Interface distante : CalculRemote

 

67.5. Les EJB de type Session

Les EJB Session sont généralement utilisés comme façade pour proposer des fonctionnalités qui peuvent faire appel à d'autres composants ou entités tels que des EJB session, des EJB Entity, des POJO, ...

La version 3.0 des EJB rend inutile l'implémentation d'une interface spécifique à l'API EJB. Mais même si cela n'est pas obligatoire, il est fortement recommandé (dans la mesure du possible) de définir une interface dédiée à l'EJB qui va notamment préciser son mode d'accès et les méthodes utilisables.

Cette interface est alors une simple POJI.

 

67.5.1. L'interface distante et/ou locale

Un EJB peut être invoqué :

  • en local : le client appelant est exécuté dans la même JVM que celle de l'EJB. Ce type d'appel est le plus performant puisqu'il ne nécessite pas d'échanges réseaux et donc pas de mécanisme pour gérer ces échanges
  • à distance : le client appelant est exécuté dans une autre JVM que celle de l'EJB

L'interface distante définit les méthodes qui peuvent être appelées par un client en dehors de la JVM du conteneur. L'interface ou le bean doit être marqué avec l'annotation @Remote implémentée dans la classe javax.ejb.Remote

L'interface locale définit les méthodes qui peuvent être appelées par un autre EJB s'exécutant dans la même JVM que le conteneur. Les performances sont ainsi accrues car les mécanismes de protocoles d'appels distants ne sont pas utilisés (sérialisation/désérialisation, RMI, ...).

L'utilisation de l'interface Local pour des appels à l'EJB dans un même JVM est fortement recommandée car cela améliore les performances de façon importante. L'interface Remote met en oeuvre des mécanismes de communication utilisant la sérialisation, ce qui dégrade les performances notamment de façon inutile si l'appel à l'EJB se fait dans une même JVM.

Un client ne dialogue jamais en direct avec une instance de l'EJB : le client utilise toujours l'interface pour accéder au bean grâce à un proxy généré par le conteneur. Même un client local utilise un proxy particulier dépourvu des accès réseau. Ce proxy permet au conteneur d'assurer certaines fonctionnalités comme la sécurité et les transactions.

 

67.5.2. Les beans de type Stateless

Les beans de type stateless sont les plus simples et les plus véloces car le conteneur gère un pool d'instances qui sont utilisées au besoin, ce qui évite des opérations d'instanciation et de destruction à chaque utilisation. Ceci permet une meilleure montée en charge de l'application.

L'annotation @javax.ejb.Stateless permet de préciser qu'un EJB session est de type stateless. Elle s'utilise sur une classe qui encapsule un EJB et possède plusieurs attributs :

Attribut

Rôle

String name

Nom de l'EJB (optionnel)

String mappedName

Nom sous lequel l'EJB sera mappé. La valeur par défaut est le nom non qualifié de la classe (optionnel)

String description

Description de l'EJB (optionnel)


Il faut définir l'interface de l'EJB avec l'annotation précisant le mode d'accès.

L'annotation @javax.ejb.Remote permet de préciser que l'EJB pourra être accédé par des clients distants. Elle s'utilise sur une classe qui encapsule un EJB ou l'interface qui décrit les fonctionnalités de l'EJB utilisables à distance. Cette annotation ne peut être utilisée que pour des EJB sessions.

Elle possède un seul attribut :

Attribut

Rôle

Class[] value

Préciser la liste des interfaces distantes de l'EJB. Son utilisation est obligatoire si la classe de l'EJB implémente plusieurs interfaces différentes java.io.Serializable, java.io.Externalizable ou une des interfaces du package javax.ejb (optionnel)


Exemple :
import javax.ejb.Remote;

@Remote
public interface CalculRemote {
  public long additionner(int valeur1, int valeur2);
}

Cette interface est marquée avec l'annotation @Remote, elle permet un appel distant et définit la méthode additionner.

Remarque : l'utilisation de l'annotation rend inutile l'utilisation de la clause throws RemoteException des versions antérieures des EJB.

L'annotation @javax.ejb.Local permet de préciser que l'EJB pourra être accédé par des clients locaux de la JVM. Elle s'utilise sur une classe qui encapsule un EJB ou l'interface qui décrit les fonctionnalités de l'EJB utilisables en local dans la JVM. Cette annotation ne peut être utilisée que pour des EJB sessions.

Elle possède un attribut :

Attribut

Rôle

Class[] value

Préciser la liste des interfaces distantes de l'EJB. Son utilisation est obligatoire si la classe de l'EJB implémente plusieurs interfaces différentes java.io.Serializable, java.io.Externalizable ou une des interfaces du package javax.ejb (optionnel)


Exemple :
import javax.ejb.Local;

@Local
public interface CalculLocal {
  public long additionner(int valeur1, int valeur2);
}

Cette interface est marquée avec l'annotation @Local pour permettre un appel local et définir la méthode additionner.

Il faut ensuite définir la classe de l'EJB qui va contenir les traitements métiers.

Exemple :
import javax.ejb.*;

@Stateless
public class CalculBean implements CalculRemote, CalculLocal {
  public long additionner(int valeur1, int valeur2) {
    return valeur1 + valeur2;
  }
}

Cette classe est marquée avec l'annotation @Stateless et implémente les interfaces distante et locale précédemment définies.

Il est préférable lorsque cela est possible d'utiliser l'interface Local car elle est beaucoup plus performante. L'interface Remote est à utiliser lorsque le client n'est pas dans la même JVM.

Les annotations @Local et @Remote peuvent être utilisées directement sur l'EJB mais il est préférable de définir une interface par mode d'accès et d'utiliser l'annotation adéquate sur chacune des interfaces.

La classe de l'EJB ne doit plus implémenter l'interface javax.ejb.SessionBean qui était obligatoire avec les EJB 2.x. Maintenant, les EJB Session de type stateless peuvent utiliser les callbacks d'évènements marqués avec les annotations suivantes :

  • @PostConstruct
  • @PreDestroy

 

67.5.3. Les beans de type Stateful

Les beans de type Stateful sont capables de conserver leur état durant toute leur utilisation par le client. Cet état n'est cependant pas persistant : les données sont perdues à la fin de son utilisation ou à l'arrêt du serveur. Un exemple type d'utilisation de ce type de bean est l'implémentation d'un caddie pour un site de vente en ligne.

L'annotation @javax.ejb.Stateful permet de préciser qu'un EJB Session est de type Stateful. Elle s'utilise sur une classe qui encapsule un EJB.

Elle possède plusieurs attributs :

Attribut

Rôle

String name

Nom de l'EJB (optionnel)

String mappedName

Nom sous lequel l'EJB sera mappé. La valeur par défaut est le nom non qualifié de la classe (optionnel)

String description

Description de l'EJB (optionnel)


Le conteneur EJB a la possibilité de sérialiser/désérialiser des EJB de type Stateful notamment dans le cas où la JVM du conteneur commence à manquer de mémoire. Dans ce cas, le conteneur peut sérialiser des EJB (passivate) qui ne sont pas en cours d'utilisation sur le disque. Dès qu'un de ces EJB sera sollicité, le conteneur va le déserialiser (activate) à partir du disque pour le remettre en mémoire et pouvoir l'utiliser.

Les EJB 3 n'ont plus à implémenter l'interface javax.ejb.SessionBean comme c'était le cas dans les versions antérieures. Maintenant, les EJB session de type stateful peuvent utiliser les callbacks d'évènements marqués avec les annotations suivantes :

  • @PostConstruct
  • @PostActivate
  • @PreDestroy
  • @PrePassivate
  • @Remove

 

67.5.4. L'invocation d'un EJB Session par un service web

Le support des services web dans les EJB 3.0 repose essentiellement sur JAX-WS 2.0 et SAAJ qu'il faut privilégier au détriment de JAX-RPC qui est toujours supporté.

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

 

67.5.5. L'utilisation des exceptions

Les exceptions personnalisées qui sont utilisées dans les interfaces métiers des EJB doivent être annotées avec @javax.ejb.ApplicationException qui s'utilise donc sur une classe encapsulant une exception métier.

Une exception annotée avec @ApplicationException sera directement envoyée au client par le conteneur.

Elle possède un seul attribut x:

Attribut

Rôle

boolean rollback

Préciser si le conteneur doit effectuer un rollback si cette exception est levée. La valeur par défaut est false (optionnel)


L'attribut rollback de l'annotation @ApplicationException de type booléen permet de préciser si la levée de l'exception va déclencher ou non un rollback de la transaction en cours. La valeur par défaut est false, signifiant qu'il n'y aura pas de rollback.

Exemple :
package com.jmd.test.domaine.ejb;

import javax.ejb.ApplicationException;

@ApplicationException
public class ErreurMetierException extends Exception {

  public ErreurMetierException() {
  }

  public ErreurMetierException(String msg) {
    super(msg);
  }
}

L'annotation @ApplicationException peut être utilisée avec des exceptions de type checked et unchecked.

 

67.6. Les EJB de type Entity

Dans les versions antérieures des EJB, les EJB de type Entity avaient la charge de la persistance des données. Les EJB de type Entity CMP (Container Managed Persistence) nécessitent simplement un fichier de description.

Les EJB 3.0 proposent d'utiliser l'API Java Persistence pour assurer la persistance des données dans les EJB : ils utilisent un modèle de persistance léger standard en remplacement des entity beans de type CMP.

JPA repose sur des beans entity qui sont de simples POJO enrichis d'annotations permettant de mettre en oeuvre les concepts de POO tels que l'héritage ou le polymorphisme.

Jusqu'à la version 3.0 des EJB, les Entity beans sont des composants qui dépendent pleinement du conteneur d'EJB du serveur d'applications dans lequel ils s'exécutent. L'utilisation de POJO avec l'API Java Persistence permet de rendre les beans entity indépendants du conteneur. Ceci présente plusieurs avantages dont celui de pouvoir facilement tester les beans puisqu'ils ne requièrent plus de conteneur pour leur exécution.

Avec la version 3.0 des EJB, les beans entity sont donc des POJO qui n'ont pas besoin d'implémenter une interface spécifique aux EJB, doivent posséder un constructeur sans argument et implémenter l'interface Serializable.

Les attributs persistants sont déclarés par des annotations soit au niveau de l'attribut soit au niveau de son getter/setter. De ce fait, ils peuvent être utilisés directement comme objets du domaine ; il n'y a plus l'obligation de définir un DTO.

 

67.6.1. La création d'un bean Entity

Les beans de type Entity sont dans la version 3.0 des spécifications de simple POJO utilisant les annotations de l'API Java Persistence (JPA) pour définir le mapping.

Les informations de mapping entre une table et un objet peuvent être définies grâce aux annotations mais aussi par un fichier de mapping qui permet d'externaliser les informations du POJO. Il est possible de mixer les deux (annotations et fichiers de mapping) mais les données incluses dans le fichier sont prioritaires sur les annotations.

Le bean entity doit être annoté avec l'annotation @Entity implémentée dans la classe javax.persistence.Entity.

L'annotation @Table implémentée dans la classe javax.persistence.Table permet de préciser le nom de la table vers laquelle le bean sera mappé. L'utilisation de cette annotation est facultative si le nom de la table correspond au nom de la classe.

Pour mapper un champ de la table avec une propriété du bean, il faut utiliser l'annotation @Column implémentée dans la classe javax.persistence.Column sur le getter de la propriété. L'utilisation de cette annotation est facultative si le nom du champ correspond au nom de la propriété.

Le champ correspondant à la clé primaire de la table doit être annoté avec l'annotation @Id implémentée dans la classe javax.persistence.Id. L'utilisation de cette annotation est obligatoire car un identifiant unique est obligatoire pour chaque occurrence et l'API n'a aucun moyen de déterminer le champ qui encapsule cette information.

Il peut être pratique pour un bean de type entity d'implémenter l'interface Serializable : le bean pourra être utilisé dans les paramètres et la valeur de retour des méthodes métiers d'un EJB. Le bean peut ainsi être utilisé pour la persistance et le transfert de données.

Exemple :
package com.jmd.test.domaine.entity;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name = "PERSONNE")
@NamedQueries({@NamedQuery(name = "Personne.findById", 
  query = "SELECT p FROM Personne p WHERE p.id = :id"), 
               @NamedQuery(name = "Personne.findByNom", 
  query = "SELECT p FROM Personne p WHERE p.nom = :nom"), 
               @NamedQuery(name = "Personne.findByPrenom", 
  query = "SELECT p FROM Personne p WHERE p.prenom = :prenom")})
public class Personne implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @Column(name = "ID", nullable = false)
  private Integer id;
  @Column(name = "NOM")
  private String nom;
  @Column(name = "PRENOM")
  private String prenom;

  public Personne() {
  }

  public Personne(Integer id) {
    this.id = id;
  }

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public String getPrenom() {
    return prenom;
  }

  public void setPrenom(String prenom) {
    this.prenom = prenom;
  }

  @Override
  public String toString() {
    return "com.jmd.test.domaine.entity.Personne[id=" + id + "]";
  }
}

Remarque : il est préférable de définir tous les beans de type entity dans un package dédié.

La mise en oeuvre précise de l'API JPA est proposée dans le chapitre qui lui est consacré.

 

67.6.2. La persistance des entités

La version 3.0 propose une refonte complète des EJB entités afin de simplifier leur développement. Cette simplification est assurée en grande partie par la mise en oeuvre de JPA qui permet :

  • la standardisation du mapping O/R
  • l'utilisation de POJO annotés avec support de l'héritage et du polymorphisme
  • la possibilité d'utiliser les EJB entités en dehors du conteneur d'EJB ce qui permet notamment la mise en oeuvre de tests automatisés

La persistance d'objets avec JPA repose sur plusieurs fonctionnalités :

  • un ensemble d'entités annotées qui représente le modèle objet du domaine
  • une API contenue dans le package javax.persistence
  • un cycle de vie pour les entités

La classe EntityManager est responsable de la gestion des opérations sur une entité notamment grâce à plusieurs méthodes :

  • persist()
  • remove()
  • merge()
  • flush()
  • find()
  • refresh()
  • ...

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

67.6.3. La création d'un EJB Session pour manipuler le bean Entity

Le bean entity n'est utilisé que pour le mapping. Pour réaliser des opérations avec le bean entity, il faut développer un EJB Session qui va encapsuler la logique des traitements à réaliser avec le bean entity.

Il faut définir l'interface Local et/ou Remote des méthodes métiers de l'EJB.

Il faut ensuite définir l'EJB qui va utiliser l'API Java Persistence et le bean entity.

L'injection de dépendances est utilisée pour obtenir une instance de l'EntityManager par le conteneur.

Exemple :
        @PersistenceContext
        private EntityManager em;

L'annotation @PersistenceContext demande au conteneur d'injecter une instance de la classe EntityManager.

Le conteneur retrouve l'EntityManager grâce au nom de l'unité de persistance fourni comme valeur à la propriété unitName de l'annotation si plusieurs unités de persistance sont définies.

L'instance de type EntityManager peut être utilisée dans les méthodes métiers pour réaliser des traitements sur le bean entity.

Exemple :
...
    public void create(Personne personne) {
        em.persist(personne);
    }

    public void edit(Personne personne) {
        em.merge(personne);
    }

    public void remove(Personne personne) {
        em.remove(em.merge(personne));
    }

    public Personne find(Object id) {
        return em.find(com.jmd.test.domaine.entity.Personne.class, id);
    }

...

 

67.7. Un exemple simple complet

L'exemple de cette section va développer un EJB métier effectuant des opérations de type CRUD sur une table nommée personne et permettant l'appel de cet EJB par un service web.

La table personne contient trois champs :

  • id : identifiant unique de la personne
  • nom : nom de la personne
  • prenom : prénom de la personne

Cette table est stockée dans une base de données de type JavaDB.

Une connexion vers la base de données est définie dans l'annuaire sous le nom MaTestDb

Remarque : les sources de cet exemple sont générées par l'IDE Netbeans.

 

67.7.1. La création de l'entité

La classe Personne encapsule une entité sur la table personne.

Exemple :
package com.jmd.test.domaine.entity;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name = "PERSONNE")
@NamedQueries({@NamedQuery(name = "Personne.findById", 
  query = "SELECT p FROM Personne p WHERE p.id = :id"), 
               @NamedQuery(name = "Personne.findByNom", 
  query = "SELECT p FROM Personne p WHERE p.nom = :nom"), 
               @NamedQuery(name = "Personne.findByPrenom", 
  query = "SELECT p FROM Personne p WHERE p.prenom = :prenom")})
public class Personne implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @Column(name = "ID", nullable = false)
  private Integer id;
  @Column(name = "NOM")
  private String nom;
  @Column(name = "PRENOM")
  private String prenom;

  public Personne() {
  }

  public Personne(Integer id) {
    this.id = id;
  }

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public String getPrenom() {
    return prenom;
  }

  public void setPrenom(String prenom) {
    this.prenom = prenom;
  }

  @Override
  public String toString() {
    return "com.jmd.test.domaine.entity.Personne[id=" + id + "]";
  }
}

 

67.7.2. La création de la façade

L'interface métier locale est définie dans l'interface PersonneFacadeLocal

Exemple :
package com.jmd.test.domaine.ejb;

import com.jmd.test.domaine.entity.Personne;
import java.util.List;
import javax.ejb.Local;

@Local
public interface PersonneFacadeLocal {

  void create(Personne personne);

  void edit(Personne personne);

  void remove(Personne personne);

  Personne find(Object id);

  List<Personne> findAll();
}

L'interface métier distante est définie dans l'interface PersonneFacadeRemote

Exemple :
package com.jmd.test.domaine.ejb;

import com.jmd.test.domaine.entity.Personne;
import java.util.List;
import javax.ejb.Remote;

@Remote
public interface PersonneFacadeRemote {

  void create(Personne personne);

  void edit(Personne personne);

  void remove(Personne personne);

  Personne find(Object id);

  List<Personne> findAll();
}

La façade est implémentée sous la forme d'un EJB de type stateless.

Exemple :
package com.jmd.test.domaine.ejb;

import com.jmd.test.domaine.entity.Personne;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class PersonneFacade implements PersonneFacadeLocal, PersonneFacadeRemote {
  @PersistenceContext
  private EntityManager em;

  public void create(Personne personne) {
    em.persist(personne);
  }

  public void edit(Personne personne) {
    em.merge(personne);
  }

  public void remove(Personne personne) {
    em.remove(em.merge(personne));
  }

  public Personne find(Object id) {
    return em.find(com.jmd.test.domaine.entity.Personne.class, id);
  }

  public List<Personne> findAll() {
    return em.createQuery("select object(o) from Personne as o").getResultList();
  }
}

Les fonctionnalités offertes par l'EJB sont de type CRUD.

Le fichier persistence.xml demande simplement l'utilisation de la connexion définie dans l'annuaire.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
    http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="EnterpriseApplication3-ejbPU" transaction-type="JTA">
    <jta-data-source>MaTestDb</jta-data-source>
    <properties/>
  </persistence-unit>
</persistence>

 

67.7.3. La création du service web

Pour permettre une meilleure séparation des rôles de chaque classe, le service web est développé dans une classe dédiée.

Exemple :
package com.jmd.test.services;

import com.jmd.test.domaine.ejb.PersonneFacadeLocal;
import com.jmd.test.domaine.entity.Personne;
import java.util.List;
import javax.ejb.EJB;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.ejb.Stateless;

@WebService()
@Stateless()
public class PersonneWS {
  @EJB
  private PersonneFacadeLocal ejbRef;

  @WebMethod(operationName = "create")
  @Oneway
  public void create(Personne personne) {
    ejbRef.create(personne);
  }

  @WebMethod(operationName = "edit")
  @Oneway
  public void edit(Personne personne) {
    ejbRef.edit(personne);
  }

  @WebMethod(operationName = "remove")
  @Oneway
  public void remove(Personne personne) {
    ejbRef.remove(personne);
  }

  @WebMethod(operationName = "find")
  public Personne find(Object id) {
    return ejbRef.find(id);
  }

  @WebMethod(operationName = "findAll")
  public List<Personne> findAll() {
    return ejbRef.findAll();
  }
}

L'injection de dépendances est utilisée pour laisser le conteneur fournir au service web une référence sur l'instance de l'EJB.

 

67.8. L'utilisation des EJB par un client

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

67.8.1. Pour un client de type application standalone

Un client distant est en mesure d'utiliser des EJB possédant une interface de type Remote : dans ce cas, plusieurs opérations sont à réaliser

  • Connexion au serveur JNDI
  • Recherche de l'interface Remote de l'EJB dans JNDI
  • Récupération du proxy grâce à JNDI
  • Utilisation de l'EJB au travers du proxy

Les informations nécessaires à la connexion à l'annuaire JNDI du serveur d'applications sont spécifiques à chaque implémentation du serveur.

Le nom de stockage de l'interface dans JNDI est aussi spécifique au serveur utilisé.

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

67.8.2. Pour un client de type module Application Client Java EE

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

67.9. L'injection de dépendances

Le conteneur peut être utilisé pour assurer l'injection de dépendances de certaines ressources requises par exemple par un contexte de persistance ou un autre EJB.

L'injection de dépendances est réalisée au moment de l'instanciation du bean par le conteneur.

L'injection de dépendances permet de simplifier le travail des développeurs : il n'est plus nécessaire d'invoquer l'annuaire du serveur par JNDI et de caster le résultat pour obtenir une instance de la dépendance. C'est le conteneur lui-même qui va s'en charger grâce à des annotations déchargeant le développeur de l'écriture du code utilisant JNDI ou un objet de type EJBContext.

Plusieurs annotations sont définies pour mettre en oeuvre cette injection de dépendances :

  • @EJB : permet d'injecter une référence vers un autre EJB
  • @Ressource : injecter une dépendance vers une ressource externe : DataSources JDBC, destinations JMS (queue ou topic), ... (annotation de Java 5)
  • @PersistenceContext : injecter un objet de type EntityManager
  • @WebServiceRef : injecter une référence vers un service web (annotation de JAX-WS)

Les annotations relatives à l'injection de dépendances peuvent être utilisées sur des variables d'instance ou sur des méthodes de type setter.

 

67.9.1. L'annotation @javax.ejb.EJB

L'annotation @EJB permet de demander au conteneur d'injecter une référence sur un EJB sans avoir à faire appel explicitement à l'annuaire du serveur avec JNDI.

Exemple :
    @EJB 
    private PersonneFacadeLocal ejbReference;

Le conteneur utilise par défaut le type de la variable pour déterminer le type de l'instance de l'EJB qui sera injectée.

Elle s'utilise sur une classe, une méthode ou une propriété :

  • sur une propriété : il est possible d'annoter un objet du type de l'EJB. L'EJB injecté sera du type de l'objet annoté.
  • sur une méthode : il est possible d'annoter une méthode de type setter sur la classe de l'EJB. L'EJB injecté sera du type de l'objet en paramètre de la méthode.
  • sur une classe : cela permet de déclarer que l'EJB sera utilisé à l'exécution

Elle possède plusieurs attributs :

Attribut

Rôle

Class beanInterface

Nom de l'interface de l'EJB

String beanName

Nom de l'EJB (correspond à l'attribut name des annotations @Stateless et @Stateful). Par défaut, c'est le nom de la classe de l'EJB

String description

Description de l'EJB

String mappedName

Nom JNDI de l'EJB. Cet attribut n'est pas portable

String name

Nom avec lequel l'EJB sera recherché


L'injection de dépendances est réalisée entre l'assignation de l'EJBContext et le premier appel d'une méthode de l'EJB.

S'il y a une ambiguïté pour déterminer le type de l'EJB à injecter, il est possible d'utiliser les attributs beanName et mappedName de l'annotation @EJB pour désigner l'EJB concerné.

 

67.9.2. L'annotation @javax.annotation.Resource

L'annotation @javax.annotation.Resource permet d'injecter des instances de ressources gérées par le conteneur telles qu'une datasource JDBC ou une destination JMS (queue ou topic) par exemple.

Cette annotation est utilisable sur une classe, une méthode ou un champ. Si l'annotation est utilisée sur une méthode ou un champ, le conteneur injecte les références au moment de l'initialisation du bean.

Elle possède plusieurs attributs :

Attribut

Rôle

String name

Nom de la ressource

Class type

Type de la ressource

AuthenticationType authenticationType

Type d'authentification à utiliser pour accéder à la ressource. Les valeurs possibles sont AuthenticationType.CONTAINER et AuthenticationType.APPLICATION

boolean shareable

Indiquer si la ressource peut être partagée entre cet EJB et d'autres EJB. Ne s'applique que sur certains types de ressources

String mappedName

Nom JNDI de la ressource

String description

Description de la ressource

 

67.9.3. Les annotations @javax.annotation.Resources et @javax.ejb.EJBs

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

 

67.9.4. L'annotation @javax.xml.ws.WebServiceRef

L'annotation @WebServiceRef possède plusieurs attributs :

Attribut

Rôle

String name

Nom JNDI de la ressource

String wsdlLocation

URL pointant sur le WSDL du service web

Class type

Type Java

Class value

La classe du service qui doit obligatoirement hériter de javax.xml.ws.Service

String mappedName

 

Pour définir les mêmes fonctionnalités dans le descripteur de déploiement, il faut utiliser le tag <service-ref>.

 

67.10. Les intercepteurs

Un intercepteur est une méthode qui sera exécutée selon deux types d'événements :

  • intercepteur pour l'invocation de méthodes métiers
  • intercepteur pour des événements liés au cycle de vie de l'EJB

Un intercepteur permet de définir des traitements, généralement transverses, qui seront exécutés lorsque ces événements surviendront. Leur rôle est similaire à certaines fonctionnalités de base de l'AOP (programmation orientée aspect)

Les intercepteurs sont utilisables avec des EJB Session et MessageDriven.

Les annotations dédiées, utilisées pour la mise en oeuvre des intercepteurs, sont regroupées dans le package javax.interceptor :

  • AroundInvoke
  • ExcludeClassInterceptors
  • DefaultInterceptors
  • Interceptors

 

67.10.1. Le développement d'un intercepteur

Un intercepteur permet de définir des traitements sous la forme de méthodes qui seront exécutées soit à l'invocation d'une méthode métier soit lors d'événements liés au cycle de vie de l'EJB. Son rôle est similaire à certaines fonctionnalités de base proposées par l'AOP.

Un intercepteur peut être défini soit :

  • dans un EJB : dans ce cas il ne concerne que cet EJB
  • dans une classe intercepteur : dans ce cas, il pourra être utilisé par tous les EJB qui en feront la demande en utilisant l'annotations @javax.interceptor.Interceptors

La signature des méthodes de callback diffère selon la nature de l'intercepteur :

  • dans la classe d'un EJB : la signature est void nomMethode()
  • dans la classe d'un intercepteur : la signature est void nomMethode(InvocationContext)

 

67.10.1.1. L'interface InvocationContext

L'interface javax.interceptor.InvocationContext définit les fonctionnalités pour permettre d'utiliser un contexte lors de l'invocation d'un ou plusieurs intercepteurs.

Cette interface définit plusieurs méthodes :

Méthode

Rôle

Object getTarget()

Renvoyer l'instance de l'EJB

Method getMethod()

Renvoyer la méthode métier de l'EJB qui a provoqué l'invocation de l'intercepteur. Si l'invocation est liée au cycle de vie de l'EJB alors cette méthode renvoie null

Object[] getParameters()

Renvoyer un tableau des paramètres de la méthode du bean pour laquelle l'intercepteur a été invoqué

void setParameters(Object[])

Modifier les paramètres qui seront utilisés pour l'invocation de la méthode

Map<String,Object> getContextData()

Obtenir une collection des données associées à l'invocation du callback

Object proceed()

Invoquer le prochain intercepteur de la chaîne ou de la méthode métier de l'EJB si tous les intercepteurs ont été invoqués


Une instance de type InvocationContext est passée en paramètre des intercepteurs.

Il est ainsi possible d'échanger des données entre les invocations des intercepteurs définis pour une même méthode.

Attention : une instance d'InvocationContext n'est pas partageable entre un intercepteur pour des méthodes métiers et un intercepteur pour des événements liés au cycle de vie des EJB.

 

67.10.1.2. La définition d'un intercepteur lié aux méthodes métiers

L'annotation @AroundInvoke permet de marquer une méthode qui sera exécutée lors de l'invocation des méthodes métiers d'un EJB. Cette annotation ne peut être utilisée qu'une seule fois dans une même classe d'un intercepteur ou d'un EJB. Il n'est pas possible d'annoter une méthode métier avec l'annotation @AroundInvoke.

La signature d'une méthode annotée avec @AroundInvoke doit être de la forme :

Object nomMethode(InvocationContext) throws Exception

Une méthode annotée avec @AroundInvoke doit toujours invoquer la méthode proceed() de l'instance de type InvocationContext fournie en paramètre pour permettre l'invocation d'éventuels autres intercepteurs assossiés à la méthode.

Exemple :
package com.jmd.test.domaine.ejb;

import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

/**
 * Intercepteur qui calcule le temps d'exécution d'une méthode métier
 * @author jmd
 */
public class MesurePerfIntercepteur {

  @AroundInvoke
  public Object mesurerPerformance(InvocationContext ic) throws Exception {
    long debutExec = System.currentTimeMillis();
    try {
      return ic.proceed();
    } finally {
      long tempsExec = System.currentTimeMillis() - debutExec;
      System.out.println("[PERF] Temps d'execution de la methode " + ic.getClass() 
        + "." + ic.getMethod() + " : " + tempsExec + " ms");
    }
  }
}

L'exemple ci-dessus permet de définir un intercepteur qui logguera les temps d'exécutions des méthodes métiers des EJB.

 

67.10.1.3. La définition d'un intercepteur lié au cycle de vie

Un intercepteur peut être exécuté lorsque certains événements liés au cycle de vie de l'EJB tels que la création, la destruction, la passivation ou la réactivation surviennent.

Les EJB 2.x imposaient l'implémentation de méthodes d'une interface telles que ejbCreate(), ejbPassivate(), ... Avec les EJB 3.x, ces méthodes peuvent avoir un nom quelconque du moment qu'elles sont annotées avec une annotation liée à un événement du cycle de vie de l'EJB. Les annotations pour définir des callbacks sur des invocations de méthodes liées au cycle de vie de l'EJB sont :

  • @javax.annotation.PostConstruct : méthode invoquée par le conteneur lorsqu'il a terminé les injections de dépendances pour un EJB et avant le premier appel à une des méthodes métiers du bean
  • @javax.annotation.PreDestroy : méthode invoquée par le conteneur juste avant que l'EJB ne soit définitivement détruit
  • @javax.ejb.PrePassivate : méthode invoquée par le conteneur lorsqu'un EJB de type session stateful va être rendu inactif (EJB session de type stateful uniquement)
  • @javax.ejb.PostActivate : méthode invoquée par le conteneur lorsqu'un EJB de type session stateful va être réactivé (EJB session de type stateful uniquement)

Il est possible dans une même classe d'utiliser plusieurs de ces annotations mais il n'est pas possible d'utiliser plusieurs fois la même dans une même classe.

Une méthode annotée avec une annotation liée au cycle de vie dans une classe d'un intercepteur doit invoquer la méthode proceed() de l'instance de type InvocationContext fournie en paramètre pour permettre l'invocation des traitements liés à l'état courant du cycle de vie de l'EJB.

 

67.10.1.4. La mise en oeuvre d'une classe d'un intercepteur

Une classe d'intercepteurs est un simple POJO qui doit obligatoirement avoir un constructeur sans paramètre et dont certaines méthodes sont annotées avec l'annotation @AroundInvoke ou avec une annotation liée au cycle de vie de l'EJB.

Exemple :
package com.jmd.test.domaine.ejb;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

public class MonIntercepteur {

  @AroundInvoke
  public Object audit(InvocationContext ic)
        throws Exception {
    System.out.println("MonIntercepteur Invocation de la methode : " + ic.getMethod());
    return ic.proceed();
  }

  @PreDestroy
  public void preDestroy(InvocationContext ic) {
    System.out.println("MonIntercepteur suppression du bean : " + ic.getTarget());
  }

  @PostConstruct
  public void postConstruct(InvocationContext ic) {
    System.out.println("MonIntercepteur Creation du bean : " + ic.getTarget());
  }
}

Les intercepteurs peuvent avoir accès par injection de dépendances aux ressources gérées par le conteneur (EJB, EntityManager, destination JMS, ...).

Un intercepteur métier peut lever une exception applicative puisque les méthodes métiers peuvent lever une exception dans leur clause throws.

Les intercepteurs définis dans la classe de l'EJB sont exécutés après les intercepteurs précisés par l'annotation @Interceptors.

 

67.10.2. Les intercepteurs par défaut

Il est possible de définir des intercepteurs par défaut qui seront appliqués à tous les EJB d'un même jar.

La définition d'un intercepteur par défaut ne peut se faire que dans le descripteur de déploiement ejb-jar.xml. Ils ne peuvent pas être définis par des annotations.

Pour déclarer un intercepteur par défaut, il faut modifier le descripteur de déploiement ejb-jar.xml en utilisant un tag <interceptor-binding> fils du tag <assembly-descriptor>.

Le tag <interceptor-binding> peut avoir deux tags fils :

  • <ejb-name> dont la valeur précise un filtre sur les ejb-name qui indique les EJB concernés. La valeur * permet d'indiquer que tous les EJB sont concernés.
  • <interceptor-class> permet de préciser la classe pleinement qualifiée de l'intercepteur

L'intercepteur doit être déclaré dans un tag <interceptor> fils du tag <interceptors>. Le tag fils <interceptor-class> permet de préciser le nom pleinement qualifié de la classe de l'intercepteur.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns = "http://java.sun.com/xml/ns/javaee" 
         version = "3.0" 
         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_0.xsd">
  <interceptors>
    <interceptor>
      <interceptor-class>com.jmd.test.domaine.ejb.MesurePerfIntercepteur</interceptor-class>
    </interceptor>
  </interceptors>
    
  <assembly-descriptor>
    <interceptor-binding>
      <ejb-name>*</ejb-name>
      <interceptor-class>com.jmd.test.domaine.ejb.MesurePerfIntercepteur</interceptor-class>
    </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>

Les intercepteurs par défaut sont toujours invoqués avant les autres intercepteurs.

Pour empêcher l'invocation d'un intercepteur par défaut pour un EJB, il faut l'annoter avec l'annotation @javax.interceptor.excludeDefaultInterceptors.

Ces intercepteurs offrent donc deux avantages :

  • ils peuvent s'appliquer à tout ou partie des EJB contenus dans le même jar que l'intercepteur
  • la description de leur application est centralisée et configurable dans le descripteur de déploiement ce qui permet facilement de la modifier (exemple : désactiver un intercepteur par défaut)

 

67.10.3. Les annotations des intercepteurs

Plusieurs annotations peuvent être utilisées lors de la mise en oeuvre des intercepteurs. Celles-ci sont soit des annotations dédiées contenues dans le package javax.interceptor soit des annotations standards de l'API Java contenues dans le package javax.annotation.

 

67.10.3.1. L'annotation @javax.annotation.PostContruct

L'annotation @javax.annotation.Postconstruct permet de définir un intercepteur qui est lié à l'événement de création du cycle de vie de l'EJB.

La méthode annotée avec cette annotation sera invoquée par le conteneur après l'initialisation de l'EJB et l'injection des dépendances mais avant l'appel de la première méthode.

Elle peut être utilisée dans la classe d'un intercepteur ou dans la classe d'un EJB mais dans les deux cas, une seule méthode peut être annotée avec cette annotation.

Elle s'utilise sur une méthode dont la signature doit respecter quelques contraintes :

  • elle ne doit pas avoir de valeur de retour
  • elle ne peut pas lever d'exception de type checked
  • elle ne peut pas être ni static ni final
  • elle ne possède aucun attribut.

 

67.10.3.2. L'annotation @javax.annotation.PreDestroy

L'annotation @javax.annotation.PreDestroy permet de définir un intercepteur qui est lié à l'événement de suppression du cycle de vie de l'EJB.

La méthode annotée avec cette annotation sera invoquée par le conteneur avant que l'EJB ne soit détruit du conteneur. Celle-ci pourra par exemple procéder à la libération de ressources.

Elle peut être utilisée dans la classe d'un intercepteur ou dans la classe d'un EJB mais dans les deux cas, une seule méthode peut être annotée avec cette annotation.

Elle s'utilise sur une méthode dont la signature doit respecter quelques contraintes :

  • elle ne doit pas avoir de valeur de retour
  • elle ne peut pas lever d'exception de type checked
  • elle ne peut pas être ni static ni final
  • elle ne possède aucun attribut.

 

67.10.3.3. L'annotation @javax.interceptor.AroundInvoke

L'annotation @javax.interceptor.AroundInvoke permet de définir un intercepteur qui est lié à l'exécution de méthodes métiers. Cette annotation peut être utilisée dans la classe d'un intercepteur ou dans la classe d'un EJB mais dans les deux cas, une seule méthode peut être annotée avec cette annotation.

Elle s'utilise sur une méthode. Elle ne possède aucun attribut.

 

67.10.3.4. L'annotation @javax.interceptor.ExcludeClassInterceptors

L'annotation @javax.interceptor.ExcludeClassInterceptors permet de demander d'inhiber l'invocation des intercepteurs pour une méthode. L'inhibition ne concerne pas les intercepteurs par défaut.

Elle s'utilise sur une méthode. Elle ne possède aucun attribut

 

67.10.3.5. L'annotation @javax.interceptor.ExcludeDefaultInterceptors

L'annotation @javax.interceptor.ExcludeDefaultInterceptors permet d'inhiber l'invocation des intercepteurs par défaut. Utilisée sur la classe d'un bean, cette annotation inhibe l'invocation des intercepteurs par défaut pour toutes les méthodes métiers du bean. Utilisée sur une méthode d'un bean, l'inhibition se limite à cette méthode.

Cette annotation s'utilise sur une classe ou une méthode.

 

67.10.3.6. L'annotation @javax.interceptor.Interceptors

L'annotation @javax.interceptor.Interceptors permet de définir les classes d'intercepteurs qui seront invoquées par le conteneur. Si plusieurs classes d'intercepteurs sont définies alors elles seront invoquées dans l'ordre de leur définition dans l'annotation.

Si l'annotation est utilisée sur la classe du bean alors les intercepteurs seront invoqués pour chaque méthode du bean. Si l'annotation est utilisée sur une méthode alors les intercepteurs seront invoqués uniquement pour la méthode.

Les intercepteurs sont invoqués dans un ordre précis :

  • les intercepteurs par défaut
  • les intercepteurs au niveau classe
  • les intercepteurs au niveau méthode

Elle s'utilise sur une classe ou une méthode. Elle possède un seul attribut :

Attribut

Rôle

Class[] value

Préciser un tableau de classes d'intercepteurs. L'ordre des intercepteurs dans le tableau définit leur ordre d'invocation (obligatoire)

 

67.10.4. L'utilisation d'un intercepteur

Chaque EJB qui souhaite utiliser un intercepteur devra l'ajouter grâce à l'annotation @javax.interceptor.Interceptors.

Exemple :
package com.jmd.test.domaine.ejb;

import com.jmd.test.domaine.entity.Personne;
import java.util.List;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
@Interceptors({ com.jmd.test.domaine.ejb.MonIntercepteur.class}) 
public class PersonneFacade implements PersonneFacadeLocal, PersonneFacadeRemote {
  @PersistenceContext
  private EntityManager em;

  public void create(Personne personne) {
    em.persist(personne);
  }

... 

  public List<Personne> findAll() {
    return em.createQuery("select object(o) from Personne as o").getResultList();
  }
}

Lors du lancement du serveur d'applications et de l'appel de cet EJB, les traces suivantes sont affichées dans la console du serveur d'applications :

Résultat :
...
Creation du bean
: com.jmd.test.domaine.ejb.PersonneFacade@1697e2a
...
Invocation de la
methode : public java.util.List
com.jmd.test.domaine.ejb.PersonneFacade.findAll()
...

Plusieurs intercepteurs peuvent être indiqués par cette annotation : leur ordre d'exécution sera celui dans lequel ils sont précisés dans l'annotation.

Un intercepteur est toujours exécuté dans la même transaction et le même contexte de sécurité que la méthode qui est à l'origine de son invocation.

Par défaut, si l'intercepteur est défini au niveau de la classe de l'EJB, toutes les méthodes concernées de l'EJB provoqueront l'invocation de l'intercepteur par le conteneur.

Si l'intercepteur est défini au niveau d'une méthode, l'intercepteur ne sera exécuté qu'à l'invocation de la méthode annotée.

L'annotation @javax.interceptor.ExcludeClassInterceptors sur une méthode permet de demander que l'exécution des intercepteurs de type @AroundInvoke précisés dans l'annotation @Interceptors soit ignorée pour la méthode.

L'annotation @javax.interceptorExcludeDefaultInterceptors sur une classe ou une méthode permet de demander que l'exécution des intercepteurs par défaut soit ignorée.

Il est possible d'associer un intercepteur à un EJB dans le descripteur de déploiement, donc sans utiliser l'annotation @Interceptors.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns = "http://java.sun.com/xml/ns/javaee" 
         version = "3.0" 
         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_0.xsd">
  <interceptors>
    <interceptor>
      <interceptor-class>com.jmd.test.domaine.ejb.MesurePerfIntercepteur</interceptor-class>
    </interceptor>
  </interceptors>
    
  <assembly-descriptor>
    <interceptor-binding>
      <ejb-name>PersonneFacade</ejb-name>
      <interceptor-class>com.jmd.test.domaine.ejb.MesurePerfIntercepteur</interceptor-class>
    </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>

 

67.11. Les EJB de type MessageDriven

Les EJB de type MessageDriven permettent de réaliser des traitements asynchrones exécutés à la réception d'un message dans une queue JMS.

Ils ne proposent pas d'interface locale ou distante et ne peuvent pas être utilisés comme un service web. Pour connecter le bean à une queue JMS, il faut que le bean implémente l'interface javax.jms.MessageListener.

Cette interface définit la méthode onMessage(Message).

 

67.11.1. L'annotation @ javax.ejb.MessageDriven

L'annotation @ javax.ejb.MessageDriven permet de préciser qu'un EJB est de type MessageDriven. Elle s'utilise sur une classe qui encapsule un EJB.

L'annotation @MessageDriven possède plusieurs attributs optionnels :

Attribut

Rôle

ActivationConfigProperty[] activationConfig

Préciser les informations de configuration (type de endpoint, destination (queue ou topic), mode d'aquittement des messages, ...) sous la forme d'un tableau d'annotations de type @javac.ejb.ActivationConfigProperty (optionnel)

String description

Description de l'EJB (optionnel)

String mappedName

Nom sous lequel l'EJB sera mappé. Peut aussi être utilisé pour désigner le nom JNDI de la destination utilisée (optionnel)

Class messageListenerInterface

Préciser l'interface de type message Listener. Il faut utiliser cet attribut si l'EJB n'implémente pas d'interface ou implémente plusieurs interfaces différentes de java.io.Serializable, java.io.Externalizable ou une ou plusieurs interfaces du package javax.ejb. La valeur par défaut est Object.class (optionnel)

String name

Nom de l'EJB. La valeur par défaut est le nom non qualifié de la classe (optionnel)

 

67.11.2. L'annotation @javax.ejb.ActivationConfigProperty

Les paramètres nécessaires à la configuration de l'EJB notamment le type et la destination sur laquelle le bean doit écouter doivent être précisés grâce à l'attribut activationConfig. Cet attribut est un tableau d'objets de type ActivationConfigProperty.

L'annotation @javax.ejb.ActivationConfigProperty permet de préciser le nom et la valeur d'une propriété de la configuration des EJB de type MessageDriven. Elle s'utilise dans la propriété activationConfig d'une annotation de type javax.ejb.MessageDriven.

Elle possède plusieurs attributs :

Attribut

Rôle

String propertyName

Préciser le nom de la propriété (obligatoire)

String propertyValue

Préciser la valeur de la propriété (obligatoire)


Exemple :
@MessageDriven(mappedName = "jms/MonEJBQueue", activationConfig = {
  @ActivationConfigProperty(propertyName="acknowledgeMode", propertyValue="Auto-acknowledge"),
  @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue")
})

 

67.11.3. Un exemple d'EJB de type MDB

L'exemple ci-dessous est un EJB de type MessageDriven qui écoute sur une queue nommée jms/MonEJBQueue et qui affiche sur la console le contenu des messages de type texte reçus dans la queue.

Exemple :
package com.jmd.test.domaine.ejb;

import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(mappedName = "jms/MonEJBQueue", activationConfig = {
  @ActivationConfigProperty(propertyName="acknowledgeMode", propertyValue="Auto-acknowledge"),
  @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue")
})
public class MonEJBMessageBean implements MessageListener {

  @Resource
  private MessageDrivenContext mdc;

  public MonEJBMessageBean() {
  }

  public void onMessage(Message message) {

    TextMessage msg = null;
    try {
      if (message instanceof TextMessage) {
        msg = (TextMessage) message;
        System.out.println("Message recu = " + msg.getText());
      }
    } catch (JMSException e) {
      e.printStackTrace();
      mdc.setRollbackOnly();
    } catch (Throwable te) {
      te.printStackTrace();
    }
  }
}

Il est possible d'écrire un client de test par exemple sous la forme d'une servlet. Cette servlet peut utiliser l'injection de dépendances si elle s'exécute dans le même serveur d'applications.

Exemple :
package com.jmdoudoux.test.servlet;

import java.io.*;
import java.net.*;

import javax.annotation.Resource;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.servlet.*;
import javax.servlet.http.*;

public class EnvoyerMessage extends HttpServlet {

  @Resource(mappedName = "jms/MonEJBQueue")
  Queue queue = null;
    
  @Resource(mappedName = "jms/MonEJBQueueFactory")
  QueueConnectionFactory factory = null;
    
  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    try {
      out.println("<html>");
      out.println("<head>");
      out.println("<title>Servlet EnvoyerMessage</title>");
      out.println("</head>");
      out.println("<body>");
      out.println("<h1>Servlet EnvoyerMessage</h1>");
      out.println("<form>");
      out.println("Message : <input type='text' name='msg'><br/>");
      out.println("<input type='submit'><br/>");
      out.println("</form>");
      out.println("</body>");
      out.println("</html>");

      String msg = request.getParameter("msg");

      if (msg != null) {
        QueueConnection connection = null;
        QueueSession session = null;
        MessageProducer messageProducer = null;
        try {
          connection = factory.createQueueConnection();
                    
          session = connection.createQueueSession(false,
                    QueueSession.AUTO_ACKNOWLEDGE);
          messageProducer = session.createProducer(queue);

          TextMessage message = session.createTextMessage();
          message.setText(msg);
          messageProducer.send(message);
          messageProducer.close();
          connection.close();
        } catch (JMSException ex) {
          ex.printStackTrace();
        }
      }
    } finally {
      out.close();
    }
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    processRequest(request, response);
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    processRequest(request, response);
  }

  public String getServletInfo() {
    return "Envoi d'un message pour test EJB de type MDB";
  }
}

Si le client ne s'exécute pas dans le même serveur d'applications, il faut utiliser JNDI pour obtenir la queue et la fabrique de connexions.

Exemple :
package com.jmdoudoux.test.servlet;

import java.io.*;
import java.net.*;

import javax.annotation.Resource;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.servlet.*;
import javax.servlet.http.*;

public class EnvoyerMessage extends HttpServlet {

  Queue queue = null;
  QueueConnectionFactory factory = null;
    
  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    try {
      out.println("<html>");
      out.println("<head>");
      out.println("<title>Servlet EnvoyerMessage</title>");
      out.println("</head>");
      out.println("<body>");
      out.println("<h1>Servlet EnvoyerMessage</h1>");
      out.println("<form>");
      out.println("Message : <input type='text' name='msg'><br/>");
      out.println("<input type='submit'><br/>");
      out.println("</form>");
      out.println("</body>");
      out.println("</html>");

      String msg = request.getParameter("msg");

      if (msg != null) {
        QueueConnection connection = null;
        QueueSession session = null;
        MessageProducer messageProducer = null;
        try {
          InitialContext ctx = new InitialContext();
          queue = (Queue) ctx.lookup("jms/MonEJBQueue");
          factory = (QueueConnectionFactory) ctx.lookup("jms/MonEJBQueueFactory");
                    
          connection = factory.createQueueConnection();
          session = connection.createQueueSession(false,
            QueueSession.AUTO_ACKNOWLEDGE);
          messageProducer = session.createProducer(queue);

          TextMessage message = session.createTextMessage();
          message.setText(msg);
          messageProducer.send(message);
          messageProducer.close();
          connection.close();
        } catch (JMSException ex) {
          ex.printStackTrace();
        } catch (NamingException ex) {
          ex.printStackTrace();
        }
      }
    } finally {
      out.close();
    }
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
  }

  public String getServletInfo() {
    return "Envoi d'un message pour test EJB de type MDB";
  }
}

Il est important que la queue et la fabrique de connexions soient définies dans l'annuaire du serveur d'applications.

Exemple avec GlassFish : extrait du fichier sun-resources.xml

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.
  //DTD Application Server 9.0 Resource Definitions 
  //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
...
  <admin-object-resource enabled="true" jndi-name="jms/MonEJBQueue" 
    object-type="user" res-adapter="jmsra" res-type="javax.jms.Queue">
    <description/>
    <property name="Name" value="PhysicalQueue"/>
  </admin-object-resource>
  <connector-resource enabled="true" jndi-name="jms/MonEJBQueueFactory" 
    object-type="user" pool-name="jms/MonEJBQueueFactoryPool">
    <description/>
  </connector-resource>
...
</resources>

Les EJB de type MessageDriven peuvent exploiter toutes les fonctionnalités de JMS : utilisation d'une queue ou d'un topic comme destination, utilisation des différents types de messages (TextMessage, ObjectMessage, ...)

 

67.12. Le packaging des EJB

Les EJB doivent être packagés dans une archive de type jar qui contiendra tous les éléments nécessaires à leur exécution.

Le fichier jar peut lui-même être incorporé dans une archive de type EAR (Enterprise Archive) qui regroupe plusieurs modules dans une même entité (EJB, application web, application client lourde, ...).

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

67.13. Les transactions

Une transaction exécute une succession d'unités de traitements qui utilisent une ou plusieurs ressources, le plus souvent une base de données. Ces unités de traitements forment un ensemble d'activités qui interagit pour former un tout fonctionnel : leurs exécutions doivent toutes réussir ou aucune ne doit être exécutée.

Le but d'une transaction est de s'assurer que toutes les unités de traitements qu'elle inclut seront correctement exécutées ou qu'aucune ne le sera si un problème survient.

Une transaction permet d'assurer l'intégrité des données car soit elle s'exécute correctement dans son intégralité soit elle ne fait aucune modification.

Une transaction possède quatre caractéristiques connues sous l'acronyme ACID :

  • Atomic : l'exécution doit être correcte dans son intégralité ou ne pas avoir d'effet. Chaque unité de traitement doit être exécutée sans erreur : si une erreur survient alors toutes les modifications réalisées dans les précédentes unités d'exécution doivent être annulées pour revenir à l'état initial
  • Consistent : le développeur doit s'assurer que les modifications réalisées dans une transaction doivent être consistantes. Par exemple, lors d'une opération bancaire de transfert de fond entre deux comptes, le montant du débit et du crédit sur chacun des comptes doit être identique
  • Isolated : les données mises à jour dans la transaction ne doivent pas être modifiées en dehors de la transaction durant son execution
  • Durable : le résultat de l'exécution correcte de la transaction doit être rendu persistant

L'abandon d'une transaction ne doit donc pas simplement se limiter à son arrêt, il est aussi obligatoire d'annuler toutes les mises à jour déjà réalisées par la transaction pour permettre de laisser le système dans son état initial au lancement de la transaction.

 

67.13.1. La mise en oeuvre des transactions dans les EJB

Le conteneur d'EJB propose un support des transactions par déclaration ce qui évite d'avoir à mettre en oeuvre explicitement une API de gestion des transactions dans le code.

Dans les EJB, une transaction concerne une méthode d'un EJB : cette transaction inclut tous les traitements contenus dans la méthode. Ceci inclut donc aussi les appels aux méthodes d'autres EJB sous réserve de leur déclaration de participation à une transaction.

La transaction est aussi propagée au contexte de persistance assuré par les EntityManager. Si la transaction est validée, alors le contexte de persistance va rendre persistante les modifications effectuées durant la transaction.

Tous les traitements inclus dans la transaction définissent la portée de la transaction.

Lorsque la transaction est gérée par le conteneur, la décision de valider ou d'abandonner la transaction est prise par le conteneur. Une transaction est abandonnée si une exception est levée dans les traitements de la méthode ou par une des méthodes de l'EJB appelées dans ses traitements.

 

67.13.2. La définition de transactions

L'annotation @TransactionAttribute implémentée dans la classe javax.ejb.TransactionAttribute ou le descripteur des EJB permet de mettre en oeuvre les transactions par déclaration. Ceci transforme les caractéristiques de la transaction sans avoir à modifier le code des traitements dans la méthode de l'EJB.

 

67.13.2.1. La définition du mode de gestion des transactions dans un EJB

L'annotation javax.ejb.TransactionManagement permet de préciser le mode de gestion des transactions dans un EJB de type session ou message driven. Ce mode peut prendre deux valeurs :

  • gestion par le container (valeur par défaut)
  • gestion par le code de l'EJB

Elle s'utilise sur une classe d'un EJB session ou message driven.

Elle possède un attribut :

Attribut

Rôle

TransactionManagementType value

Préciser le mode de gestion des transactions dans l'EJB. Cet attribut peut prendre deux valeurs :

  • TransactionManagementType.CONTAINER (valeur par défaut)
  • TransactionManagementType.BEAN

Dans le cas où le mode précisé est BEAN, il est nécessaire de coder la gestion des transations dans les méthodes qui en ont besoin en utilisant l'API JTA.

 

67.13.2.2. La définition de transactions avec l'annotation @TransactionAttribute

L'annotation @javax.ejb.TransactionAttribute permet de préciser dans quel contexte transactionnel une méthode d'un EJB sera invoquée. Cette annotation est incompatible avec la valeur BEAN de l'annotation TransactionManagement.

Elle s'utilise sur une classe d'un EJB ou sur une méthode d'un EJB session. Utilisée sur une classe, l'annotation s'applique à toutes les méthodes de l'EJB.

Elle possède un attribut :

Attribut

Rôle

TransactionAttributeType value

Préciser le contexte transactionnel d'invocation d'une méthode de l'EJB. Cet attribut peut prendre plusieurs valeurs :

  • TransactionAttributeType.MANDATORY
  • TransactionAttributeType.REQUIRED (valeur par défaut)
  • TransactionAttributeType.REQUIRES_NEW
  • TransactionAttributeType.SUPPORTS
  • TransactionAttributeType.NOT_SUPPORTED
  • TransactionAttributeType.NEVER

L'annotation @TransactionAttribute peut prendre différentes valeurs :

  • NOT_SUPPORTED : suspend la propagation de la transaction aux traitements de la méthode et des appels aux autres EJB de ces traitements. Une éventuelle transaction démarrée avant l'appel d'une méthode marquée avec cet attribut est suspendue jusqu'à la sortie de la méthode.
  • SUPPORTS : la méthode est incluse dans une éventuelle transaction démarrée avant son appel. Cet attribut permet à la méthode d'être incluse ou non dans une transaction
  • REQUIRED : la méthode doit obligatoirement être incluse dans une transaction. Si une transaction est démarrée avant l'appel de cette méthode, alors la méthode est incluse dans la portée de la transaction. Si aucune transaction n'est définie à l'appel de la méthode, le conteneur va créer une nouvelle transaction dont la portée concernera les traitements de la méthode et les appels aux EJB de ces traitements. La transaction prend fin à la sortie de la méthode (valeur par défaut lorsque l'annotation n'est pas utilisée ou définie dans le fichier de déploiement)
  • REQUIRES_NEW : une nouvelle transaction est systématiquement démarrée même si une transaction est démarrée lors de l'appel de la méthode. Dans ce cas, la transaction existante est suspendue jusqu'à la fin de l'exécution de la méthode
  • MANDATORY : la méthode doit obligatoirement être incluse dans la portée d'une transaction existante avant son appel. Aucune transaction ne sera créée et elle doit obligatoirement être fournie par le client appelant. L'appel de la méthode non incluse dans la portée d'une transaction lève une exception de type javax.ejb.EJBTransactionRequiredException
  • NEVER : la méthode ne doit jamais être appelée dans la portée d'une transaction. Si c'est le cas, une exception de type EJBException est levée

Cette annotation peut être utilisée au niveau de l'EJB (dans ce cas, toutes les méthodes de l'EJB utilisent la même déclaration des attributs de transaction) ou au niveau de chaque méthode.

 

67.13.2.3. La définition de transactions dans le descripteur de déploiement

Les déclarations des attributs relatives aux transactions peuvent aussi être faites dans le descripteur de déploiement. Le tag <container-transaction> est utilisé pour préciser les attributs de transaction d'une ou plusieurs méthodes d'un EJB.

Pour un EJB, le tag fils <method> permet de préciser la ou les méthodes concernées. Le tag fils <ejb-name> indique l'EJB. Le tag <method-name> permet de préciser la méthode concernée ou toutes les méthodes de l'EJB en mettant * comme valeur du tag.

Le tag fils <trans-attribute> permet de préciser l'attribut de transaction à utiliser.

 

67.13.2.4. Des recommandations sur la mise en oeuvre des transactions

Il est fortement recommandé d'utiliser un contexte de persistance (EntityManager) dans la portée d'une transaction afin de s'assurer que tous les accès à la base de données se font dans un contexte transactionnel. Ceci implique d'utiliser les attributs de transaction Required, Requires_New ou Mandatory.

Un EJB de type MessageDriven ne peut utiliser que les attributs de transaction NotSupported et Required. L'attribut NotSupported précise que les messages ne seront pas traités dans une transaction. L'attribut Required précise que les messages seront traités dans une transaction créée par le conteneur.

Il n'est pas possible d'utiliser l'attribut Mandatory avec un EJB qui est proposé sous la forme d'un service web.

La gestion des attributs d'une transaction est importante car l'utilisation d'un EJB dans un contexte transactionnel est coûteuse en ressources. Il faut bien tenir compte du fait que la valeur par défaut des attributs de transaction est utilisée si aucun attribut n'est précisé et que cet attribut par défaut est REQUIRED, ce qui place automatiquement l'EJB dans un contexte transactionnel.

Il est donc fortement recommandé d'utiliser un attribut de transaction NotSupported lorsqu'aucune transaction n'est requise.

 

67.14. La mise en oeuvre de la sécurité

Les autorisations reposent en Java EE sur la notion de rôle. Un ou plusieurs rôles sont affectés à un utilisateur. L'attribution des autorisations se fait donc au niveau rôle et non au niveau utilisateur.

Même s'il est possible d'utiliser une API dédiée, généralement la mise en oeuvre de la sécurité dans les EJB se fait de manière déclarative.

Seuls les EJB de type Session peuvent être sécurisés.

Pour définir des restrictions, il faut utiliser le descripteur de déploiement ou les annotations dédiées. Ces restrictions reposent sur la notion de rôle.

Lorsqu'une méthode est invoquée et que le conteneur détecte une violation des restrictions d'accès alors ce dernier lève une exception de type javax.ejb.EJBAccessException qui devra être traitée par le client appelant.

 

67.14.1. L'authentification et l'identification de l'utilisateur

Lorsqu'un client utilise des fonctionnalités du conteneur d'EJB, il possède un identifiant de sécurité durant sa connexion. L'authentification de l'utilisateur est à la charge de l'application cliente.

La façon dont l'utilisateur est fourni au conteneur est dépendante de l'implémentation des EJB utilisés. Généralement cela se fait en passant des propriétés lors de la recherche du contexte JNDI.

Certains serveurs d'applications utilisent des mécanismes plus complexes et plus riches fonctionnellement en mettant en oeuvre l'API JAAS par exemple.

Lors de l'invocation des méthodes des EJB, cet identifiant de sécurité est passé implicitement à chaque appel pour permettre au conteneur de vérifier les autorisations d'utilisation par l'utilisateur.

 

67.14.2. La définition des restrictions

La définition des restrictions d'accès permet la mise en oeuvre des mécanismes d'autorisation.

Lorsqu'un utilisateur invoque un EJB, le conteneur contrôle les autorisations d'exécution de la méthode invoquée en comparant le ou les rôles de l'utilisateur avec le ou les rôles autorisés à exécuter cette méthode.

Le mécanisme d'autorisations est précisement défini dans les spécifications des EJB. La définition des autorisations peut être déclarée de deux façons différentes :

  • utilisation des annotations dans le code de classe des EJB
  • utilisation du descripteur de déploiement

 

67.14.2.1. La définition des restrictions avec les annotations

Par défaut, toutes les méthodes publiques d'un EJB peuvent être invoquées sans restriction de sécurité.

La définition de restrictions d'accès à un EJB se fait principalement grâce à l'annotation @javax.annotation.security.RolesAllowed qui permet de préciser les rôles qui seront autorisés à invoquer la méthode de l'EJB.

L'annotation @RolesAllowed peut s'utiliser :

  • sur la classe de l'EJB : dans ce cas, cela définit les restrictions par défaut pour toutes les méthodes de l'EJB
  • sur une méthode de l'EJB : dans ce cas, cela définit les restrictions pour la méthode en remplaçant les restrictions par défaut déjà définies

L'annotation @PermitAll permet l'invocation par tout le monde : c'est l'annotation par défaut si aucune restriction n'est définie.

L'annotation @DenyAll permet d'empêcher l'invocation d'une méthode quel que soit le rôle de l'utilisateur qui l'invoque.

L'annotation @RunAs permet de forcer le rôle sous lequel l'EJB est exécuté dans le conteneur. Cette annotation ne fait aucun contrôle d'accessibilité.

 

67.14.2.2. La définition des restrictions avec le descripteur de déploiement

La définition de la configuration de sécurité incluant les rôles et les restrictions d'accès peut être réalisée en tout ou partie dans le descripteur de déploiement.

Les restrictions d'accès sont définies dans un tag <method-permission>

Le tag <method-permission> peut avoir plusieurs tags fils :

  • un ou plusieurs tags <role-name> qui permettent de préciser un rôle autorisé à utiliser la méthode
  • le tag <unchecked> qui est équivalent à l'annotation @PermitAll
  • un tag <method> qui précise la ou les méthodes concernées

Le tag <method> possède plusieurs tags fils :

  • <ejb-name> qui précise le nom de l'EJB concerné
  • <method-name> qui précise la méthode ou toutes les méthodes en utilisant le caractère *. Remarque : il n'est pas possible de combiner l'utilisation de caractères avec le caractère *
  • <method-params> qui est optionnel permet de déterminer les méthodes concernées en cas de surcharge. Chacun des paramètres est défini avec un tag <method-param> qui contient le type du paramètre
  • <method-intf> qui est optionnel permet de préciser l'interface d'accès. Les valeurs possibles sont : Remote, Local, Home, LocalHome et ServicePoint
  • <description> qui est optionnel permet de fournir une description

Pour empêcher l'accès à certaines méthodes, il faut utiliser le tag <exclude-list> fils du tag <assembly-descriptor>. Le tag <exclude-list> a un rôle équivalent à l'annotation @DenyAll. Chaque méthode concernée est décrite avec un tag fils <method>.

 

67.14.3. Les annotations pour la sécurité

Les spécifications des EJB 3.0 définissent plusieurs annotations pour gérer et mettre en oeuvre la sécurité dans les accès réalisés sur les EJB.

 

67.14.3.1. javax.annotation.security.DeclareRoles

L'annotation @DeclareRoles permet de définir la liste des rôles qui sont utilisés par un EJB pour sécuriser l'invocation de ses méthodes.

Cette annotation est utile pour préciser les rôles au conteneur dans le cas où les restrictions d'accès sont définies par programmation. Elle peut aussi être utilisée pour fournir explicitement au conteneur la liste des rôles implicitement définis dans les annotations @RolesAllowed.

L'annotation @DeclareRoles s'applique uniquement sur une classe. Elle ne possède qu'un seul attribut :

Attribut

Rôle

String[] value

Préciser le ou les rôles utilisés lors du contrôle d'accès à l'EJB (obligatoire)

 

67.14.3.2. javax.annotation.security.DenyAll

Aucun client ne peut invoquer la méthode de l'EJB qui est marquée avec cette annotation.

L'annotation @DenyAll s'applique uniquement sur une méthode. Elle ne possède pas d'attribut.

 

67.14.3.3. javax.annotation.security.PermitAll

L'annotation @PermitAll permet de préciser que la ou les méthodes de l'EJB n'ont aucune restriction d'accès.

L'annotation @PermitAll s'applique sur une classe ou une méthode. Elle ne possède pas d'attribut.

Cette annotation est l'annotation par défaut pour un EJB si aucune restriction d'accès n'est explicitement définie.

 

67.14.3.4. javax.annotation.security.RolesAllowed

L'annotation @RolesAllowed permet de préciser les rôles qui seront autorisés à invoquer une ou plusieurs méthodes d'un EJB.

L'annotation @RolesAllowed s'applique sur une classe ou une méthode.

Appliquée à une classe, cette annotation définit les restrictions d'accès par défaut de toutes les méthodes de l'EJB

Appliquée à une méthode, cette annotation définit les restrictions d'accès pour la méthode en remplaçant les éventuelles restrictions par défaut.

Elle ne possède qu'un seul attribut :

Attribut

Rôle

String[] value

Préciser le ou les rôles qui peuvent invoquer la ou les méthodes (obligatoire)

 

67.14.3.5. javax.annotation.security.RunAs

L'annotation @RunAs permet de préciser le rôle sous lequel un EJB va être executé dans le conteneur indépendemment du rôle de l'utilisateur qui invoque l'EJB.

L'annotation @RunAs s'utilise sur la classe d'un EJB. Elle possède un seul attribut :

Attribut

Rôle

String value

Préciser le rôle sous lequel l'EJB s'exécute (obligatoire)


Cette annotation peut être utilisée sur un EJB de type Session ou Message Driven.

 

67.14.4. La mise en oeuvre de la sécurité par programmation

L'interface EJBContext propose des fonctionnalités relatives à la mise en oeuvre de la sécurité.

La méthode javax.security.Principal getCallerPrincipal() permet de connaître l'utilisateur qui invoque l'EJB.

L'interface javax.security.Principal encapsule l'utilisateur qui invoque un EJB. Sa méthode getName() permet de connaître le nom de l'utilisateur.

La méthode boolean isCallerInRole() renvoie un booléen qui vaut true si l'utilisateur qui invoque l'EJB possède le rôle founi en paramètre.

Lorsque cette méthode est utilisée, il faut utiliser l'annotation @DeclareRoles en lui précisant en paramètre les rôles qui sont utilisés avec la méthode isCallerInRole(). Autrement, il faut effectuer la déclaration équivalente dans le descripteur de déploiement. Ceci permet au conteneur de savoir que ces rôles sont utilisés par l'EJB.

L'utilisation de ces méthodes permet de mettre en oeuvre des fonctionnalités d'autorisations plus pointues que la simple vérification vis-à-vis d'un rôle.


Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]