Rappels sur JEE 5
Le codage des EJB a été largement simplifié avec la spécification EJB 3. Les mécanismes de base sont les mêmes : RMI, JNDI, pas d’invocation directe des méthodes des EJB, cycle de vie de l’EJB géré par le conteneur. Les EJB 3 sont basés sur de simples classes POJO (Plain Old Java Object, objet de base type JavaBean). L’interface Home de création a disparu et le fichier XML ejb-jar.xml décrivant le composant n’est plus obligatoire. Cette simplification est liée à l’évolution du langage (apparition des annotations avec Java 5) et aux bénéfices issus des travaux sur les frameworks Spring, Hibernate… Les différents services d’une application doivent pouvoir se localiser. Cette opération peut prendre de multiples formes : ● création d’un objet et utilisation directe de celuici ; ● recherche du service en utilisant JNDI ; ● emploi de fabrique pour créer le service ; ● implémentation des services comme singleton. Le code client du service peut, alors, devenir très vite significativement dépendant de l’implémentation, ou vite peu lisible par le codage des recherches. Cette localisation est effectuée classiquement par l’intermédiaire de JNDI sur les serveurs d’application. Les dépendances peuvent être éliminées en confiant à un tiers le soin de mettre en relation les objets, c’est l’inversion de contrôle. Les composants sont, alors, reliés automatiquement avant leur utilisation. Pour bien comprendre comment les conteneurs JEE 5 gèrent le cycle de vie des composants, il est primordial de comprendre les implémentations possibles du pattern Inversion of Control (IoC), appelé aussi Dependency Injection. L’inversion de contrôle (IoC Inversion of Control) n’est en rien, une évolution du langage. C’est un modèle de conception qui propose de séparer les problématiques techniques (aspects) des problématiques métier dans une application. C’est une application tiers, le framework, qui mettra en liaison les objets.
Les annotations
Le système des annotations est une des évolutions importantes apportées par Java 5, puis enrichies par Java 6. C’est une véritable alternative aux fichiers de configuration XML et aux outils tiers tels que XDoclet. La maintenance est largement simplifiée car la prise en compte des changements dans le code source est, directement, sous la responsabilité des outils du langage, ou des conteneurs, sans avoir à synchroniser un fichier XML ou à réeffectuer une compilation avec XDoclet. Les annotations sont des métadonnées ajoutées au code. Le développeur ajoute les annotations au code source et elles peuvent se propager jusqu’à l’exécution. Il existe, dans la spécification des annotations, des « super annotations » : les métaannotations, qui permettent d’annoter les annotations. Ces métaannotations sont situées dans le package java.lang.annotation. L’ensemble des annotations (et métaannotations) utilisables avec le JDK 6 héritent de l’interface java.lang.annotation.Annotation. Les conteneurs, dont JBoss, ajoutent leur propre jeu d’annotations. Métaannotation Objectif @Documented Indique à l’outil javadoc qu’il doit prendre en compte cette annotation. @Inherited Permet l’héritage entre annotations. @Retention Détermine la politique de propagation de l’annotation. @Target Détermine la cible de l’annotation. La métaannotation @Retention permet de fixer le mode de propagation de l’annotation. RetentionPolicy.SOURCE : les annotations ne seront pas enregistrées dans le fichier *.class. Elles sont utilisables par les outils manipulant les fichiers sources (compilateur, javadoc…). Exemple : l’annotation standard @Overrides. RetentionPolicy.RUNTIME : les annotations sont enregistrées dans le fichier *.class et sont accessibles par la machine virtuelle, lors de l’exécution. Ces annotations sont utilisées via l’API de réflexion. Les annotations JBoss (comme @LocalBinding) sont du type RetentionPolicy.RUNTIME. RetentionPolicy.CLASS : les annotations sont enregistrées dans le fichier *.class. Elles ne sont pas utilisées par la machine virtuelle, mais peuvent l’être par les outils manipulant les fichiers *.class. L’injection de dépendance utilise les annotations et la réflexion. Un exemple simple permet de mieux comprendre comment les conteneurs peuvent injecter dans les membres de nos classes les ressources voulues (EJB, source de données…). L’ensemble des fichiers sources qui suivent sont téléchargeables sur le site ENI, sous le projet Annotations. Il nous faut d’abord créer notre propre annotation : package fr.eni.editions.jboss.annotations; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Message { String value(); } Le type de notre annotation Message est @interface. Il possède une méthode value() qui représente un attribut de notre annotation. Nous pouvons, maintenant, utiliser notre annotation dans la classe TestMessage : package fr.eni.editions.jboss.annotations; public class TestMessage { @Message(« Hello world ») private String monMessage; public void afficher() { System.out.println(« Valeur du message : « +monMessage); } } Il n’y a qu’une seule méthode dans notre annotation : value(). Nous pouvons donc éviter de préciser le nom de l’attribut de Message. Si cela n’avait pas été le cas, nous aurions dû avoir : @Message(value= »Hello world ») private String monMessage; L’objectif de l’utilisation de notre annotation est donc, d’injecter la valeur de l’attribut value de Message dans la propriété monMessage de TestMessage. Cette injection est effectuée par une application tiers, en général un framework. Dans notre exemple il s’agira d’une simple classe. Nous devons donc maintenant, coder l’injection : package fr.eni.editions.jboss.annotations; import java.util.*; import java.lang.reflect.*; public class MessageProcess { // code permettant l’injection de dépendance public static void injecter(Object obj) throws Exception { for(Field field : obj.getClass().getDeclaredFields()) La méthode injecter(Object obj) de la classe MessageProcess permet de réaliser cette injection. Pour chaque champ field de l’instance obj, il y a vérification de la présence de l’annotation. Si c’est le cas, la valeur de l’attribut value de l’annotation est récupérée pour mettre à jour le champ. C’est l’application qui mettra en œuvre l’injection : package fr.eni.editions.jboss.annotations; public class Main { public static void main(String[] args) throws Exception { TestMessage test = new TestMessage(); MessageProcess.injecter(test); test.afficher(); } } Cet exemple permet de mieux comprendre comment les conteneurs compatibles avec les spécifications JEE 5 peuvent automatiquement utiliser les annotations pour mettre des ressources dans un contexte JNDI, injecter des ressources du contexte JNDI vers les membres de nos classes. 2. Injection par proxy La journalisation, les messages de debug, les autorisations, les transactions sont des problématiques récurrentes en programmation. Si nous prenons l’exemple d’une journalisation, il nous faut une classe qui permette la journalisation, appelée ici Logger, et une qui utilise la journalisation, Service. Les méthodes ayant besoin de journaliser appelleront les méthodes de la classe Logger. Cela sera identique pour toute classe utilisant la journalisation. package fr.eni.editions.jboss.aop; public class Logger { public void debutLog() { System.out.println(« début de journalisation »); } public void finLog() { System.out.println(« fin de journalisation »); } } package fr.eni.editions.jboss.aop; public class Service { Logger logger = new Logger(); public void executer() { logger.debutLog(); System.out.println(« >> Méthode ’executer()’ »); logger.finLog(); Cette illustration peut être appliquée à l’authentification, les transactions, etc. Nous voyons ainsi notre code émaillé d’appels vers des méthodes de classes qui n’ont rien à voir avec notre code métier. Ces appels peuvent nuire à la lisibilité de nos méthodes métier. Une dépendance est créée entre des classes qui a priori n’ont rien à voir entre elles. Les services de journalisation, authentification… sont des services transversaux à notre application. Pour supprimer les dépendances entre ces classes, un des moyens est de déléguer à un framework l’appel des méthodes. Nous illustrerons ceci par l’exemple de la mise en place d’un proxy, dont la méthode invoke() sera appelée avant la méthode executer() de notre classe Service.