Introduction aux EJB3
Un article de ODcWiki.
| |
Que sont les EJB 3.0 ? La nouvelle version des très controversés Enterprise Java Beans ? une nouvelle tentative pour rattraper la confiance d’une communauté de développeurs devenue sceptique ?
Un peu "oui" mais surtout "non". La lassitude des développeurs J2EE a été pleinement entendue par Linda DeMichiel (la responsable des spécifications EJB 3.0) et son équipe. Le succès des initiatives Open Source a été pris en compte.
Cette nouvelle mouture des EJB est en fait une synthèse rigoureuse des solutions les plus populaires en matière de composants métier en Java : dans l’ADN des EJB3, on reconnaît une certaine paternité des EJB 2.X, mais on trouve aussi beaucoup de gènes Hibernate et Spring. Cet article se propose de vous faire découvrir concrètement la spécification la plus attendue de Java EE 5 Platform.
Comme pour les EJB 2.x, il existe deux grandes familles d’EJB 3.0 : les sessions beans et les entity beans. Les premiers sont là pour matérialiser des services, les seconds réifient le contenu de la base de données. Jusqu’à ce point, la tradition des EJB est respectée. Ce qui change, c’est la manière dont ces EJB sont mis en œuvre. Pour les session beans comme pour les entity beans, elle s’est considérablement simplifiée. Elle s’appuie intensivement sur les deux apports majeurs du JDK 5.0 que sont les annotations et les "generics"
Sommaire |
Le JDK 5.0 en entreprise
Avant d’entrer dans l’intimité de ces nouveaux EJB, rappelons ce que sont les annotations et les "generics" et surtout, quels rôles ils peuvent jouer dans une application d’entreprise.
Les annotations forment un système de marquage des composants élémentaires d’un programme que sont les classes, les méthodes et les attributs. Elles ressemblent en cela au célèbres Xdoclet. Elles permettent d’associer à ces composants, des informations qui pourront être exploitées par un mécanisme tiers. Par exemple, l’annotation @Overrides indique que la méthode associée doit avoir une super implémentation. Cette annotation est utilisée par le compilateur Java, par l’éditeur Java d’Eclipse, etc. L’annotation @WebService posée sur une classe Java indique au serveur d’application qu’il doit installer cette classe comme service WEB, générer le WSDL correspondant, etc. Il faut noter qu’une annotation n’apporte aucune modification du comportement per se à la classe, la méthode ou l’attribut sur lequel elle est posée. Une annotation est passive. Il est toujours nécessaire qu’un outil, un framework, un serveur d’application, reconnaisse l’annotation et apporte à la classe le comportement demandé.
Les "generics" sont un mécanisme additif au compilateur Java permettant de préciser le type des objets manipulés par une API. Ce mécanisme est d’abord utilisé dans les collections JAVA, épargnant le développeur de trop nombreux "casting". Mais les generics sont aussi connus de l’API d’introspection. Il est possible avec le JDK 5.0 de déterminer dynamiquement quels sont les éléments rassemblés par une collection donnée. Nous verrons que ce point est essentiel pour les EJB 3.0
Annotation et generics se révèlent simples à assimiler et à utiliser. Même si vous n’êtes pas familier avec le JDK 5.0, vous n’aurez probablement aucune difficulté pour comprendre les exemples de code illustrant cet article.
Définir un service à l’aide d’un Session bean
Le composant EJB qui matérialise un service métier est l’EJB Session. Comme son aïeul figurant dans la version 2.x, il doit être installé au sein d’un serveur d’application. Il confère à une procédure de traitement - sans effort de programmation – la possibilité de l’invoquer à distance, une unité transactionnelle et un contrôle d’accès sécurisé. Mais il en fait plus et pas de la même manière.
- "Plus" car il est possible désormais d’associer à ces procédures de traitement des comportements "transversaux". Il s’agit des fameux "intercepteurs" qui ont popularisé la programmation par aspect dans le monde des serveurs d’application. Et du framework Spring.
- "Différemment" car les annotations et les generics permettent de transformer des classes JAVA standard (POJO) en EJB Session, sans qu’il soit nécessaire de les faire hériter de classes (ou d’implémenter des interface) provenant de l’API des EJB, ni de définir d’obscurs méthodes réflexes (ejbActivate, ejbPassivate, etc.), ni encore de devoir définir d’interface de fabrique (les "Home" des EJB 2.x). Il n’est pas non plus nécessaire de déclarer les EJB3 Session dans un fichier de configuration de type "ejb-jar.xml".
Illustrons tout cela concrètement. Prenons comme exemple un service simpliste. Ce service met à disposition une bibliothèque de fonctions mathématiques élémentaires. La version initiale de notre classe est celle ci
public class Computer { public double add(double a, double b) {return a+b;} public double minus(double a, double b) {return a-b;} public double multiply(double a, double b) {return a*b;} public double divide(double a, double b) {return a/b;} }
Comment installer cette classe en tant que service ?
La première étape consiste à extraire de notre classe l’interface qui définit les fonctions attendues de ce service :
public interface ComputerService { public double add(double a, double b); public double minus(double a, double b); public double multiply(double a, double b); public double divide(double a, double b); }
et faire implémenter cette interface par notre classe initiale :
public class Computer implements ComputerService { ... }
Il suffit maintenant de rajouter les annotations @Remote sur l’interface et @Stateless sur la classe d’implémentation … et le tour est joué !
@Remote public interface ComputerService { … }
@Stateless public class Computer implements ComputerService { ... }
Ces deux classes sont alors placées dans une archive .jar, que l’on installe sur un serveur d’application intégrant les EJB 3.0. Au déploiement de l’application, le serveur scrute les classes contenues dans l’archive, détecte les annotations et installe automatiquement l’EJB. Aucun fichier de configuration n’est nécessaire !
Il nous reste maintenant à appeler notre service depuis un programme "client". Ce programme peut être une application externe au serveur d’application (client riche) ou une servlet. Dans l’état actuel des choses (car tout cela va changer avec la version 2.5 des servlets), il faut passer par l’annuaire du serveur l’application – c’est à dire JNDI – pour se connecter au service, à l’instar de ce que l’on faisait avec les EJB 2.x.
Notez cependant, qu’il n’est plus nécessaire de passer par une "fabrique" : le fameux "home" des EJB 2.x. La recherche du service est directe. Par défaut, le nom de l’EJB est le nom de l’interface du service auquel on ajoute "/remote" :
Computer cmp; try { InitialContext ctx = new InitialContext(); Computer cmp = (Computer) ctx.lookup("ComputerService/remote"); } catch (NamingException exp) { … } cmp.add(5.0, 5.0);
La première ligne de cette snippet crée un accesseur vers JNDI. La seconde ligne obtient le service. La troisième ligne exploite le service.
L’appel à la méthode "add" n’est pas protégée par une clause try … catch, malgré le fait que cet appel puisse être réalisé à distance. Il semble en effet, que les gens en charge de spécifier les nouvelles API de Java EE aient enfin admis que les exceptions explicites ("checked exceptions") apportent une complexité et une lourdeur que ne justifie pas le gain attendu dans le contrôle du code.
Injection de dépendance
Développons maintenant un nouveau service qui a besoin d’utiliser notre service de calcul. Ce service est aussi un session bean EJB 3 doit voici le code partiel :
@Stateless public class MiniGrid implements MiniGridService { ComputerService comp; public double computeGrid(int op, double a, double b) { switch (op) { case OperationType.ADD: return comp.add(a, b); case OperationType.REMOVE: ... } } }
L’attribut "comp" désigne le service de calcul que nous voulons exploiter. Comment l’initialiser ? Avec les EJB 2.x, il aurait fallu définir une référence dans le fichier de configuration ejb-jar.xml, que nous aurions traduite en un nom JNDI dans un fichier de configuration spécifique au serveur d’application. Cette référence devait ensuite être utilisée dans la méthode d’initialisation du bean via JNDI, Home, etc.
Pour les EJB 3.0, rien de tout cela : il suffit d’associer à "comp" l’annotation @EJB pour que le serveur d’application détecte tout seul qu’il doit lier le service MiniGrid a une instance de Computer :
@Stateless public class MiniGrid implements MiniGridService { @EJB ComputerService comp; …
Comment fait il pour trouver le bon service ? Il s’appuie sur le type de la référence "comp". Comme un unique service implémente cette interface, le serveur d’application devine tout seul la ligature à réaliser (lorsque ce n’est pas le cas, l’utilisation d’un fichier de configuration redevient nécessaire). Cette ligature n’est plus faite à l’initiative du bean (MiniGrid), elle est "injectée" en son sein par le serveur d’application, selon le Design Pattern IoC (Injection of control) ou DI (Dependency Injection). On retrouve ici le principe fondamental du framework Spring.
Définir un objet du domaine (objet métier) avec un Entity Bean
La simplification est encore plus grande en ce qui concerne les beans entités. La norme EJB 3.0 abandonne sur ce terrain l’essentiel des principes des EJB 2.x et adopte l’approche d’outils d’ORM comme Hibernate.
Les beans entités sont de simples classes Java, à l’instar de ce que présente la snippet suivante :
@Entity public class Product implements Serializable { String code; String label; ProductType type; Set<Vendor> vendors; public Product() {} public Product(String code, String label) { this.code = code; this.label = label ; } @Id public String getCode() {return code;} public void setCode(String code) { this.code = code; } public String getLabel() {return label;} public void setLabel(String label) { this.label = label; } @ManyToOne public ProductType getType() {return type;} public void setProductType(Product type) { this.type = type; } @ManyToMany(mappedBy="products") public Set<Vendor> getVendors() {return vendor;} public void setVendors(Set<Vendor> vendor) { this.vendor = vendor; } }
Cette classe ne présente rien de particulier : pas de classes à hériter, pas d’interface EJB à implémenter. Ce sont des POJOs. POJO signifie "Plain Ordinary (ou Old) Java Object", que nous pourrions traduire par "des objets Java très ordinaires". A l’exception bien sûr de quelques annotations qui vont transformer l’inoffensif POJO en un composant d’entreprise persistant en base de données.
L’annotation la plus importante est @Entity qui indique qu’il s’agit d’un bean entité. C’est en reconnaissant cette annotation que le serveur, au démarrage de l’application, va installer cette classe en tant qu’Entity bean.
L’autre annotation obligatoire est @Id qui indique quelle est la clé primaire de l’entité. Beaucoup d’autres annotations sont disponibles, permettant de définir un mapping fin avec le contenu de la base de données. L’annotation @ManyToOne donnée en exemple dans la Snippet indique qu’il faut mapper la propriété "type" avec la clé étrangère de même nom dans la table qui contient la définition des produits.
Le cas de l’annotation @ManyToMany est intéressant car il illustre le fait que les collections sont aussi gérées par les EJB 3.0 à l’aide d’annotations simples. L’attribut d’annotation "mappedBy" indique au serveur que la propriété "vendors" de Product est la relation inverse de la relation "products" de la classe Vendor. C’est par introspection sur le type des éléments contenus dans la collection retournée par "getVendors" que le serveur d’application reconnaît la classe Vendor comme cible de la relation "vendors". L’utilisation des "generics" prend tout son sens.
Aucun fichier de mapping n’est nécessaire. Le serveur d’application utilise le nom de la classe comme nom de table. Il utilise les noms des propriétés (code, label, type) comme noms de champs. Il est bien sûr possible d’altérer ce comportement par défaut en utilisant des annotations comme @Table, @Column, …
Voyons maintenant comment manipuler ces entités. A l’instar des EJB Session, les "Home" ont disparu. A la place, apparaît le gestionnaire d’entités. Ce gestionnaire est la fabrique pour toutes les entités. Il sert en quelque sorte de "Home" générique et général. Ce gestionnaire, instance de la classe "EntityManager" est initialisé dans un Session bean par injection de dépendance :
@Stateless public class MonImplementationDeDAO implements DAOService { @PersistentContext EntityManager em;
En rencontrant l’annotation @PersistentContext, le serveur d’application sait qu’il doit initialiser l’attribut associé par une instance de gestionnaire d’entités.
Les méthodes de notre Session bean peuvent maintenant utiliser le gestionnaire d’entités pour retrouver une entité particulière, dresser la liste des entités qui répondent à un critère donné, créer, modifier ou supprimer une entité, etc. Par exemple, dans la snippet suivante :
Product prd = em.find(Product.class, "is2"); List orders = em.createQuery("select o from order o where o.invoice.status = 1").getResultList(); em.persist(new Product("pF2", "pencil F2"));
La première invocation du gestionnaire d’entités recherche le produit de code "is2". La seconde invocation retourne l’ensemble des commandes dont la facture n’a pas encore été réglée. La dernière invocation insère un nouveau produit en base de données.
Ce rapide survol des beans entités permet de comprendre la nouvelle philosophie des EJB, mais il ne permet pas d’en appréhender la puissance : sachez qu’il est possible d’utiliser l’héritage des entités, de définir des requêtes paramétrées et dynamiques, d’utiliser un nouvel EJB QueryLanguage qui offre l’ensemble des possibilités de SQL, OrderBy, GroupBy, Having compris.
Le concept du "mobile object"
Contrairement aux Entités des EJB 2.x, les entités des EJB 3.0 ne peuvent être accédées à distance. Cette limitation n’en est pas vraiment une : le modèle architectural d’une application d’entreprise impose, dans la pratique, de masquer les beans entités par une série de Session beans façade. Ce modèle rend donc inutile la faculté d’un bean d’être sollicité directement.
En revanche, le fait qu’un bean entité EJB 2.x ne puisse exister hors du conteneur EJB implique l’utilisation de DTO ou "Data Objects" pour faire migrer la description d’une entité vers une application cliente. La conséquence de cet artifice est que les objets du domaine sont matérialisés par deux classes distinctes.
Rien de tout cela n’est désormais nécessaire. Les POJOs peuvent être sérialisables et un POJO peut parfaitement être utilisé en dehors d’un conteneur EJB. Il n’est donc plus nécessaire de créer des DTO. Une entité EJB 3 peut être détachée du gestionnaire d’entités, transférée dans un contexte client, modifiée dans ce contexte, puis retournée vers le serveur et enfin, réinjecté dans le gestionnaire d’entités. L’entité EJB 3.0 est mobile. La simplification est d’importance : de quatre classes (home, interface, bean, DTO) on passe à une.
Conclusion
Il y a encore quantité de choses à dire sur les EJB 3.0. Les spécifications s’étendent sur des centaines de pages. Il existe une foule d’annotations. La vision donnée ici est très limitée. La taille réduite d’un article m’interdit d’en faire plus. J’espère cependant vous avoir montré l’essentiel : un changement radical de philosophie.
Le maître mot des EJB 3.0 est simplicité. C’est aussi le maître mot du JDK 5.0 et de Java EE 5 : la cohérence est respectée. Cet objectif est atteint en combinant les approches plébiscitées par la communauté des développeurs :
- Annotations et Generics
- Approche POJO
- Injection de dépendance
- Utilisation massive de comportement par défaut
Dans un certain sens, les EJB 3.0 sont avant tout une reconnaissance et une normalisation des initiatives Hibernate et Spring. En les simplifiant ! C’est aussi la reconnaissance d’idées provenant de C# (les annotations) ou de JBoss ("defaulting" agressif). L’apport majeur de la norme EJB 3.0 est d’intégrer de manière très cohérente toutes ces idées.
Pour quel résultat ? Essayez les EJB 3.0 : Vous constaterez rapidement qu’il est désormais plus simple de développer la couche métier que de développer la couche Web …
Liens
- Migrating from EJB 2.x to EJB 3.0 explique comment transformer un EJB 2 en EJB 3 en fonction de son type: session, entité et message-driven.

