Cet article a été co-écrit par Blandine Bourgois et Julia Mateo
Cet Avant Jug a réuni peu de monde… Mais ça ne nous a pas empêché de faire connaissance et de discuter des projets des JDuchess, tel que le blog et le groupe de travail sur la certification.
Olivier Croisier et Nicolas Martignole – Photo José Paumard
Nous avons ensuite été rejoints par deux développeurs (l’Avant Jug n’est pas réservé qu’aux JDuchess) qui ont lancé le débat suivant :
«Pourquoi y-a-t-il moins de problèmes dans les équipes Agiles qui contiennent au moins une femme ? »
Olivier Croisier – The Coder's breakfast – Photo : José Paumard
Puis il est temps de partir pour les locaux de l’ISEP où la soirée va commencer. Nicolas Martignole lance un appel à contribution pour la 3ème soirée d’anniversaire du Paris Jug qui aura lieu le 28 février 2011 et dont le thème est « Siffler en travaillant ».
Cette soirée est présentée par Olivier Croisier qui est consultant Zenika. Il a aussi beaucoup d’admiratrice chez les JDuchess, car il est certifié Java 5.0 à 100%. Vous pouvez retrouver les slides et le code de la présentation sur le blog de Zenika.
Les annotations ont été introduites dans Java 5.0 pour répondre au besoin d’un système de méta-programmation robuste et flexible. Avant les annotations, le projet Xdoclet utilisait les tag JavaDoc pour générer des fichiers XML et/ou du code source.
On trouve aujourd’hui des annotations dans Java SE, Java EE et dans les frameworks modernes (Spring, Hibernate …). Mais vous pouvez aussi créer vos propres annotations.
Les annotations ont différents cas d’utilisation, par exemple :
@SuppressWarnings("unchecked")
,Il est possible de positionner des annotations sur presque tous les éléments :
Pour annoter un package correctement, il faut utiliser le fichier package-info.java et non l’annoter directement dans une classe (risque de conflit ou d’information incomplète). Ce fichier contiendra la déclaration du package accompagnée de sa JavaDoc et de ses annotations.
@Unpublished package jduchess.access.user;
Les annotations peuvent prendre des paramètres. Les règles sont les suivantes :
@MyAnnotation(msg1="value1", msg2="value2") //identique à @MyAnnotation(msg2="value2", msg1="value1") @MyAnnotationWithOnlyOneParam(value) //notation raccourcie si on a un seul paramètre //wrapper @SecondaryTables({ @SecondaryTable(name="city"), @SecondaryTable(name="country") }) @ /** commentaire pas très bien placé ! **/ Deprecated
Olivier nous explique maintenant comment créer son annotation personnalisée.
La déclaration d’une annotation est la suivante :
public @interface MyAnnotation{ }
Il est ensuite possible de compléter l’annotation avec :
Il est aussi possible, comme dans toutes les interfaces, de définir des classes, des interfaces ou des énumérations internes.
Il y a 4 méta-annotations :
@Documented
: l’annotation doit-elle être utilisée par la JavaDoc ?@Inherit
: l’annotation doit-elle être héritée ?@Retention
: quelle est la « durée de vie » de l’annotation ? @Retention prend en paramètre une valeur de l’énumération java.lang.annotation.RetentionPolicy :
CLASS
(par défaut) : Les annotations sont enregistrées dans le fichier *.class par le compilateur, mais ne sont pas utilisées par la JVM au moment de l’exécution.RUNTIME
: Les annotations sont enregistrées dans le fichier *.class par le compilateur et sont utilisées par la JVM au moment de l’exécution. Elles peuvent être lues grâce à l’API de réflection.SOURCE
: Les annotations ne sont pas enregistrées dans le fichier *.class. Elles sont prises en compte uniquement pour les outils utilisant les fichiers sources (compilateur, JavaDoc …).@Target
: L’annotation doit-elle se restreindre à certains éléments ? L’annotation @Target prend en paramètre une ou plusieurs valeurs de l’énumération java.lang.annotation.ElementType. Si le paramètre est omis, l’annotation s’applique à tous les éléments.@Documented @Inherit @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.PARAMETER}) public @interface MyAnnotation{ }
Les paramètres se déclarent sous la forme d’une méthode non générique. Voici quelques exemples :
public @interface MyAnnotation{ // paramètre String message() ; //paramètre avec une valeur par défaut int answer() default 42 ; } @MyAnnotation(message= "Hello World") @MyAnnotation(message="Hello World", answer=3) public @interface MyAnnotationWithOnlyOneParam{ //notation raccourcie si on a un seul paramètre String value() ; } @ MyAnnotationWithOnlyOneParam("Hello World") @ MyAnnotationWithOnlyOneParam(value="Hello World")
En Java 5.0 : il faut utiliser l’outil APT (Annotation Processing Tool) qui doit être lancé en plus du compilateur.
En Java 6.0 : L’API Pluggable Annotation Processing permet un traitement direct des annotations par le compilateur. Il n’est plus nécessaire d’utiliser un outil tiers. Il faut lancer la compilation en utilisant le paramètre -processor
ou utiliser le Service Provider Interface.
Ces API exécutent les processeurs d’annotations, dont le but est :
Le processus de compilation est le suivant :
Un processeur est une classe implémentant l’interface javax.annotation.processing.Processor
. Le plus souvent, on étend la classe abstraite javax.annotation.processing.AbstractProcessor
.
Deux annotations sont dédiées aux processeurs d’annotations et peuvent être utilisées sur la classe du processeur :
@SupportedAnnotationTypes
: cette annotation permet de préciser quels seront les types annotations traités par le processeur. La valeur « * » permet d’indiquer que tous les types seront traités.@SupportedSourceVersion
: cette annotation permet de préciser la version du code source traité par le processeur .Les deux méthodes à implémenter sont init()
qui permet d’initialiser le processeur et process()
.
La méthode process()
est la méthode principale qui contient les traitements à exécuter par le processeur. Elle a deux paramètres :
Set<? extends TypeElement> annotations
: Un ensemble des annotations qui seront traitées par le processeur,RoundEnvironment roundEnv
: Un objet qui encapsule l’étape courante des traitements.Cette méthode renvoie un booléen indiquant si le Set annotations a été « consommé » (traité) ou pas.
La classe AbstractProcessor
fournie des utilitaires accessibles via la propriété nommée processingEnv
de type ProcessingEnvironment
. Cette classe permet d’obtenir des instances qui offrent des traitements avec l’extérieur du processeur.
Elements, Types
: interfaces permettant une introspection du code,Filer
: interface permettant la création de nouvelles ressources (classes, fichiers de configuration …) ,Messager
: interface permettant d’envoyer des messages affichés par le compilateur.Filer filer = processingEnv.getFiler();
package com.zenika.presentation.annotations.compilation; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; import java.util.Set; @SupportedAnnotationTypes("com.zenika.presentation.annotations.compilation.*") @SupportedSourceVersion(SourceVersion.RELEASE_6) public class ListingProcessor extends AbstractProcessor { private Types types; private Elements elements; private Messager messager; private Filer filer; @Override public void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); types = processingEnv.getTypeUtils(); elements = processingEnv.getElementUtils(); messager = processingEnv.getMessager(); filer = processingEnv.getFiler(); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { //Pour chaque annotation traitée ... for (TypeElement annotation : annotations) { // Trouver les éléments portant cette annotation for (Element e : roundEnv.getElementsAnnotatedWith(annotation)) { messager.printMessage(Diagnostic.Kind.NOTE, e.getSimpleName()); } } return false; } }
Olivier Croisier – Demo, Photo : José Paumard
Ensuite Olivier nous a montré comment récupérer les annotations pendant l’exécution. Cela peut être nécessaire si, par exemple, on veut initialiser un framework ou une librairie basés sur annotations; comment est-ce que l’on pourrait savoir quelles sont les classes qui utilisent un type spécifique d’annotation? Un autre objectif serait celui de la Java Persistence API qui a besoin de récupérer les classes annotées avec @Entity
pendant l’exécution afin de définir, par exemple, le mapping ORM.
Package, Class, Constructor, Field et Method implémentent l’interface AnnotatedElement ce qui permet de récupérer facilement par introspection les annotations associées. Même les annotations liées aux paramètres d’une méthode peuvent être récupérées avec la méthode :
Annotation[][] getParameterAnnotations()
Olivier nous a présenté une petite démo qui montrait comment récupérer les annotations pour différents types d’éléments (package, classe, méthode champ et paramètre). En plus, pour avoir un exemple plus applicatif du même sujet, un exemple de comment récupérer la position d’un champ dans un fichier CSV, pendant l’exécution, grâce aux annotations, a été présenté.
Ci-dessous un extrait du code qui permet d’extraire les annotations pour différents types éléments :
@org.junit.Test public void testAnnotationDetection() { //Annotations sur le package Package pkg = Pojo.class.getPackage(); Annotation[] pkgAnnotations = pkg.getDeclaredAnnotations(); printAnnotations("Package " + pkg.getName(), pkgAnnotations); // Annotations sur la classe Annotation[] classAnnotations = Pojo.class.getDeclaredAnnotations(); printAnnotations("Classe " + Pojo.class.getName(), classAnnotations); // Annotations sur les champs Field[] fields = Pojo.class.getDeclaredFields(); for (Field field : fields) { Annotation[] fieldAnnotations = field.getDeclaredAnnotations(); printAnnotations("Champ " + field.getName(), fieldAnnotations); } // Annotations sur les méthodes Method[] methods = Pojo.class.getDeclaredMethods(); for (Method method : methods) { Annotation[] methodAnnotations = method.getAnnotations(); printAnnotations("\nMéthode " + method.getName(), methodAnnotations); // Annotations sur les paramêtres de méthodes Annotation[][] paramsAnnotations = method.getParameterAnnotations(); printParameterAnnotations("\tParamêtres", paramsAnnotations); } }
Benoît Dissert après son speech – Photo : José Paumard
Pour terminer, Olivier Croisier nous a fait une démonstration de comment injecter des annotations sur la classe Class, c’est à dire, comment créer des annotations au runtime programmatiquement. C’est un sujet un peu plus complexe dont peu de cas d’utilisation nous viennent à l’esprit mais qui peut être utile pour éviter quelques contraintes liées notamment à l’héritage des annotations.
Sans rentrer dans les détails, ci-dessous les étapes à suivre pour injecter une annotation dans une classe Class :
Nous avons beaucoup appris sur les annotations qui ne sont pas un sujet facile. Grâce aux annotations nous avons la possibilité de généraliser des comportements et de travailler avec des POJOs (EJBs 3.1 c’est un exemple). Néanmoins il faut faire attention à la complexité des solutions que nous pouvons implémenter si nous choisissons de développer nos propres annotations.
On a tendance à ne voir les annotations que comme un remplaçant des fichiers XML, or nous avons pu constater qu’elles peuvent être utiles pour d’autres objectifs tels que l’enrichissement du rôle du compilateur ou la réutilisation de code. Voici donc un outil de plus pour faire des développements élégants sur vos librairies et frameworks!
En continuant à utiliser le site, vous acceptez l’utilisation des cookies. Plus d’informations
Les paramètres des cookies sur ce site sont définis sur « accepter les cookies » pour vous offrir la meilleure expérience de navigation possible. Si vous continuez à utiliser ce site sans changer vos paramètres de cookies ou si vous cliquez sur "Accepter" ci-dessous, vous consentez à cela.