
La plate-forme Java EE offre plusieurs techniques pour développer une couche de services et d'accès aux données. La plus standard, entendez par là la plus indépendante des infrastructures d'hébergement (également appelées "conteneurs") est sans conteste les Enterprise Java Beans (EJB). Malheureusement, le développement d'EJB a longtemps été fastidieux, nécessitant même des outils de génération automatique de code basés sur des modèles UML ou sur du code Java agrémenté de commentaires spécifiques (cf. XDoclet).
L'avènement de la spécification EJB 3.0 a redistribué les cartes. En bref, cette nouvelle mouture élimine de nombreuses redondances tant au niveau du code des composants qu'au niveau de leur configuration, l'essentiel pouvant maintenant s'exprimer au sein du code source par le biais d'annotations (ou "méta-données"). Même le code client a été simplifié au passage, rendant caduque l'utilisation de la notion de Home une Abstract Factory de composants qui était implémentée par le conteneur jusqu'à la version EJB 2.1.
Même si cette spécification est encore incomplète, elle a le mérite de rendre le développement de composants enfin efficace et accessible à tout développeur Java. Dans cet article, nous partirons donc du principe qu'elle constitue une initiative constructive et élégante.
Toutefois, le développement par annotation n'est pas une technique très novatrice : nous avons déjà 5 ans de recul grâce à la plate-forme .NET qui offre ce type d'outil depuis sa première version. S'il est parfaitement adapté au paramétrage fin, on a pu s'apercevoir qu'il ne convient pas parfaitement aux paramétrages systématiques puisqu'il implique une redondance importante des annotations au niveau du code source.
Le but de cet article est de simplifier encore davantage le développement de composants EJB 3.0, d'éliminer 100% des redondances résiduelles dans le code des composants et dans leurs annotations. Pour atteindre cet objectif, nous nous appuierons sur les instruments offerts par la programmation orientée Objet, et quand cela ne suffit pas, sur ceux fournis par la programmation orientée Aspect.
L'objet n'est pas ici de décrire en détail le développement de composants EJB mais de nous donner les principes et syntaxes de base qui, comme souvent, répondent à 70 / 80% des besoins. Par exemple, la plate-forme Java EE nous propose trois types de composants : EJB Session, EJB Entité et EJB Orienté Message (MDB), mais nous nous contenterons de décrire les deux premiers.
Qu'ils soient invoqués localement (leurs clients étant situés dans la même machine virtuelle) ou à distance, les EJB Session représentent des services fonctionnels. Ils constituent les Contrôleurs des Cas d'Utilisation (sortes de Façades fonctionnelles) d'un projet, même si bien sûr un EJB Session représentant un service de très haut niveau peut reposer sur d'autres composants (d'autres EJB Session ou simplement d'autres classes Java locales) pour implémenter ses responsabilités.
Le diagramme suivant expose une architecture hybride dans laquelle les EJB Session sont invoqués :

Voilà pour le principe architectural. Prenons maintenant un exemple concret : un utilitaire de gestion de signets (bookmarks). Dans cette application, chaque utilisateur a besoin de créer des signets pour mémoriser ses sites préférés; il a également besoin d'organiser les signets par catégorie. Bien sûr, après création, il souhaitera également faire des recherches parmi ses signets, soit en parcourant les catégories, soit par mot-clé apparaissant dans le titre d'un signet.
Nous ne détaillerons pas cette application, mais à vue de nez, on peut tout de même identifier au moins deux cas d'utilisation :
Chacun de ces cas d'utilisation sera géré par un EJB Session différent; il représente un service fonctionnel de haut niveau qui sera sollicité par les utilisateurs. De plus, nous pouvons organiser notre application de telle sorte que chaque service puisse répondre aux utilisateurs sans avoir besoin de conserver leur contexte applicatif. Si cette hypothèse est avérée, il ne sera plus nécessaire d'avoir un exemplaire de composant par utilisateur : quelques instances de chaque type de composant répondront quasi-simultanément à tous les utilisateurs qui le sollicitent et ces instances pourront être recyclées pour les traiter les requêtes ultérieures. Dans la terminologie des EJB, on qualifie ce type de composant "sans mémoire" de EJB Session Stateless (par opposition à "composant à mémoire" ou EJB Session Stateful ). Une architecture technique basée essentiellement sur les Stateless aura nettement moins de problèmes de montée en charge et de répartition de charge.
Après avoir déterminé quels composants constitueraient notre application, passons à l'implémentation de l'un d'entre eux : le moteur de recherche. Voici son interface :
Son implémentation fonctionnelle ne pose donc aucun problème :
La bonne nouvelle avec les EJB 3.0, c'est que pour rendre ce composant à la fois distribué (via RMI et via WebServices) et transactionnel (car il se connectera directement ou indirectement à une base de données), il suffit de spécifier nos besoins techniques sous forme d'annotations complémentaires dans le code du Bean :
Ce dernier code mérite quelques explications:
Tous les objets métiers, aussi appelés "objets du domaine", ne sont bien entendu pas persistants (comprenez par là "à enregistrer dans un système de stockage, tel qu'une base de données par exemple"). Mais dans notre application, il se trouve que c'est le cas : les Utilisateurs, leurs Signets et les Catégories qui les structurent sont autant d'informations à conserver de manière durable. Nous choisissons ici de les stocker dans une base de données relationnelle (qui peut être HypersonicSQL, PostgreSQL, MySQL, Oracle, SQL Server, etc...)
Il existe de nombreuses techniques pour établir une connexion entre nos services (EJB Session) et une base de données relationnelle :
Grosso-modo, les deux dernières techniques d'accès à la base sont identiques. Leurs avantages comparés : l'avant-dernière permet de choisir et de maîtriser 100% du paramétrage du framework de persistance alors que les EJB Entité offrent une garantie d'indépendance et donc de portabilité vis-à-vis du conteneur (et donc du framework de persistance sous-jacent), ainsi qu'une meilleure intégration dans le contexte du serveur d'application (démarcation transactionnelle automatique et intégrée à celle des EJB Session, idem pour la sécurité...).
Dans cet article, nous allons tâcher de ne reposer que sur la plate-forme Java EE standard donc optons pour les EJB Entité. Pour être plus précis, les dernières évolutions des spécifications EJB ont clairement séparé des autres parties ce qui s'appelait hier encore encore EJB Entité. Dorénavant, nous parlerons de "composants Java Persistants", suivant le nom de la spécification JPA (Java Persistence API). Ce détail est important car il signifie que les composants persistants sont utilisables non seulement dans le contexte d'un conteneur de composants EJB mais qu'ils peuvent également être exploités dans un simple conteneur de Servlets, dans une application console ou graphique côté client (une application autonome intégrant une petite base de données par exemple), etc... Il suffit d'intégrer un gestionnaire de persistance compatible JPA (en clair : quelques bibliothèques packagées dans des fichiers JAR) pour bénéficier du support de ces composants.
Voyons maintenant comment implémenter nos composants persistants. Dans l'application de gestion des signets, nous en avions identifié 3 : Utilisateur, Signet et Catégorie. Voici les relations qui les unissent :

Et à titre d'exemple, voici le code de la classe Signet (les deux autres sont similaires). Notez l'utilisation des annotations pour exprimer l'essentiel du mapping Objet/Relationnel :
Vous l'aurez probablement deviné :
Bien sûr, en tant que tel, le paramétrage de la persistance ne suffit pas. Il nous faut également voir comment les EJB Session interagissent avec le gestionnaire de persistance pour sauvegarder ou restaurer des composants persistants vis-à-vis de la base de données. Reprenons le moteur de recherche, mais cette fois, examinons le code complet :
A la lecture du code, on se rend compte de deux choses :
Cette brève introduction aux EJB 3.0 et à JPA montre combien il est devenu simple de construire des applications distribuées (par plusieurs protocoles), transactionnelles, persistantes... tout en restant standard et portable non seulement sur plusieurs systèmes d'exploitation (c'est la force de Java) mais aussi sur n'importe quel conteneur d'EJB 3.0 (c'est la force de Java EE). Il faut avouer que la programmation par méta-données a redistribué les cartes.
Malgré toutes ces simplifications, nous pouvons nous poser les questions habituelles : que donneraient ces technologies si elles étaient utilisées sur un projet important (disons quelques centaines de composants persistants, quelques dizaines de services)? L'utilisation des annotations élimine-t-elle toutes les redondances de code? Est-il aisé de changer d'avis, par exemple sur la technique de génération de clés primaires, et de propager les modifications sur l'ensemble des composants? Bref, en une phrase : l'approche par annotation est-elle suffisante ou peut-on mieux faire?
Tout d'abord, nous pouvons considérer les clés primaires en base représentées par les identifiants uniques dans nos classes. Si l'on fait un choix technique et cohérent, il s'agit de déclarer un attribut private int id; sur toutes les classes persistantes, puis d'y apposer les annotations @Id et éventuellement @GeneratedValue comme ici. Cette redondance doit bien pouvoir être mise en facteur! Par exemple, si nous créions une classe abstraite PersistentObject, elle pourrait porter cet attribut @Id private int id.
Malheureusement, ou plutôt heureusement, la notion d'héritage entre objets persistants est supportée par JPA, ce qui signifie qu'en cas d'héritage, l'union des attributs d'une classe et de toutes ses parentes doit être enregistré pour chaque objet. Cela peut se faire dans une table unique (une table par graphe d'héritage) ou dans une table par classe persistante par exemple. Mais dans ce dernier cas, il devient évident que l'identifiant de la classe mère doit être différent de celui déclaré dans la classe fille sans quoi il serait impossible d'exprimer des corrélations et des jointures entre les tables qui mappent un graphe d'héritage. Ainsi, nous sommes bel et bien contraints de déclarer un attribut id identique mais différent dans chaque classe persistante.
Un raisonnement similaire nous amènerait conclure qu'il faut également déclarer chaque classe persistante en la préfixant de @Entity. De même, si les objets métier jouent le rôle de DTO (Data Transfer Object), c'est-à-dire si nos EJB Session les renvoient par valeur à des clients distants, il faut également les déclarer Serializable, en les faisant implémenter l'interface idoine. Le langage Java aurait pu nous éviter cette contrainte si la classe mère de toutes les autres, Object, avait été elle-même sérializable. Mais ce n'est pas le cas, donc il nous faut l'indiquer sur chaque classe (excepté sur les classes dérivant d'une autre classe déjà sérialisable, ce qui sera assez rare dans un modèle d'objets métier).
On sent bien que la programmation orientée Objet enrichie des annotations est insuffisante pour généraliser un choix d'implémentation sur un ensemble de composants. Qu'à cela ne tienne, tentons notre chance du côté des Aspects
L'utilisation d'un tisseur d'aspects statique est assez logique pour pallier les limitations énoncées dans la section précédente. Nous allons donc créer un aspect qui aura les responsabilités suivantes :
Voyons comment implémenter ces exigences avec la syntaxe AspectJ :
Pour les moins familiers de cette syntaxe biscornue, voici un décodage de l'exemple précédent : AspectJ ne permet pas d'introduire (ajouter, greffer) un attribut sur un ensemble de classes. C'est une limitation syntaxique et non conceptuelle : les déclarations de greffe doivent avoir la forme de déclarations d'attributs ou de méthode, au nom près. Par exemple ici, nous greffons l'attribut id grâce à : private int Persistent.id, ce qui signifie littéralement qu'on ajoute l'attribut id au type Persistent.
Nous utilisons donc un moyen détourné, en deux temps, qui consiste à marquer tous les types "cible" (sur lesquels nous souhaitons greffer l'attribut id) par une interface, ici Persistent. Concrètement, declare parents : metier.* implements Persistent spécifie que tous les types du package "metier" implémenteront le type Persistant (le tissage modifie donc les relations d'implémentation d'interface). Notez qu'au passage nous en avons profité pour faire hériter Persistent de Serializable. Dans un second temps, AspectJ nous permet de greffer l'attribut id à l'interface Persistent... ce qui n'a pas vraiment de sens; la signification réelle est pourtant assez proche : nous greffons l'attribut id à toutes les classes qui implémentent l'interface Persistent directement, ce sont donc bien celles que nous avons marquées dans un premier temps. L'approche est la même pour greffer les accesseurs getId() et setId() aux mêmes classes.
Les deux dernières syntaxes sont des greffes d'attributs :
Maintenant que nous avons acquiert la technique, nous pouvons la généraliser et simplifier le développement de composants EJB Session à condition de faire quelques hypothèses :
Dans cet article, nous avons vu que les spécifications EJB 3.0 et JPA simplifient énormément le développement de composants d'entreprise (distribués, transactionnels, persistants) par rapport aux spécifications précédentes. Elles s'appuient pour cela sur les nouveautés du langage Java 5.0, en particulier sur les annotations. Mais si la simplification est flagrante sur un composant, il reste malgré tout une redondance importante dans les annotations elles-mêmes lorsqu'on s'attaque à de gros projets.
Heureusement, la programmation par aspects permet de dépasser ces limitations par le biais d'un tissage statique. Le prix à payer est d'avoir une structure d'application extrêmement lisible (organisation des composants et des couches par packages) ainsi que des règles de nommage très fines (en particulier pour distinguer les méthodes nécessitant un contexte transactionnel). En acceptant de payer ce prix, on peut compléter les EJB 3.0, JPA par l'AOP et simplifier d'un cran supplémentaire le développement d'applications complexes sur la plate-forme Java EE.
Notez que la démarche suivie dans cet article est appliquable à tous les contextes dans lesquels vous serez amenés à rencontrer des redondances dans la spécification d'annotations. La programmation par annotation est un outil de spécification fin, il permet de repérer directement dans le code les choses qui sortent du cas général; mais ce n'est pas la technique la plus adaptée pour opérer des réglages ensemblistes. Pour cela, le mieux serait que les spécifications permettent une configuration globale dotée d'un langage ensembliste (comme ici : "toutes les classes du package XXX sont Persistantes et utilisent un attribut @Id id"). Mais à défaut, nous venons de voir que la Programmation Orientée Aspect s'avère être le complément idéal à la programmation par annotation car elle apporte à la programmation Objet un langage de composition ensembliste, transverse et cohérent. Et pour ne rien gâcher, puisqu'il n'existe aucune adhérence forte entre les classes du projet et les aspects, il n'est pas nécessaire pour les développeurs des couches métier et service d'apprendre à programmer en Aspects : au contraire, il leur suffit de respecter les règles de nommage sur lesquelles reposent les aspects.
N.B. Nous ne mettons pas de projet "prêt à l'emploi" à disposition avec cet article, pour éviter toute confusion : les Aspects que nous avons imaginés ici sont didactiques plus que complets ou robustes. Sur un projet d'envergure, ils sont à adapter voire à éviter dans certaines situations (en particulier celui rendant toutes les méthodes des EJB Session invoquables via les WebServices). Par contre, nous vous invitons à vous inspirer des exemples de code de cet article pour concevoir vos propres aspects. Bons tissages!