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 ]


 

9. Les annotations

 

chapitre   9

 

Niveau : niveau 3 Intermédiaire 

 

Java SE 5 a introduit les annotations qui sont des métadonnées incluses dans le code source. Les annotations ont été spécifiées dans la JSR 175 : leur but est d'intégrer au langage Java des métadonnées.

Des métadonnées étaient déjà historiquement mises en oeuvre avec Java notamment avec Javadoc ou exploitées par des outils tiers notamment XDoclet : l'outil open source XDoclet propose depuis longtemps des fonctionnalités similaires aux annotations. Avant Java 5, seul l'outil Javadoc utilisait des métadonnées en standard pour générer une documentation automatique du code source.

Javadoc propose l'annotation @deprecated qui bien qu'utilisée dans les commentaires permet de marquer une méthode comme obsolète et de faire afficher un avertissement par le compilateur.

Le défaut de Javadoc est d'être trop spécifique à l'activité de génération de documentation même si le tag deprecated est aussi utilisé par le compilateur.

Depuis leur introduction dans Java 5, les annotations sont de plus en plus utilisées dans le développement d'applications avec les plate-formes Java SE et Java EE.

Ce chapitre contient plusieurs sections :

 

9.1. La présentation des annotations

Les annotations de Java 5 apportent une standardisation des métadonnées dans un but généraliste. Ces métadonnées associés aux entités Java peuvent être exploitées à la compilation ou à l'exécution.

Java a été modifié pour permettre la mise en oeuvre des annotations :

  • une syntaxe dédiée a été ajoutée dans le langage Java pour permettre la définition et l'utilisation d'annotations.
  • le bytecode est enrichi pour permettre le stockage des annotations.

Les annotations peuvent être utilisées avec quasiment tous les types d'entités et de membres de Java : packages, classes, interfaces, constructeurs, méthodes, champs, paramètres, variables ou annotations elles-mêmes.

Java 5 propose plusieurs annotations standard et permet la création de ses propres annotations.

Une annotation précède l'entité qu'elle concerne. Elle est désignée par un nom précédé du caractère @.

Il existe plusieurs catégories d'annotations :

  • les marqueurs (markers) : ces annotations ne possèdent pas d'attribut (exemple : @Deprecated, @Override, ...)
  • les annotations paramétrées (single value annotations) : ces annotations ne possèdent qu'un seul attribut(exemple : @MonAnnotation("test") )
  • les annotations multi paramétrées (full annotations) : ces annotations possèdent plusieurs attributs (exemple : @MonAnnotation(arg1="test 3", arg2="test 2", arg3="test3") )

Les arguments fournis en paramètres d'une annotation peuvent être de plusieurs types : les chaînes de caractères, les types primitifs, les énumérations, les annotations, le type Class.

Les annotations sont définies dans un type d'annotation. Une annotation est une instance d'un type d'annotation. Les paramètres d'une annotation peuvent avoir des valeurs par défaut.

La disponibilité d'une annotation est définie grâce à une retention policy.

Les usages des annotations sont nombreux : génération de documentations, de code, de fichiers, ORM (Object Relational Mapping), ...

Les annotations ne sont guère utiles sans un mécanisme permettant leur exploitation.

Une API est proposée pour assurer ces traitements : elle est regroupée dans les packages com.sun.mirror.apt, com.sun.mirror.declaration, com.sun.mirror.type et com.sun.mirror.util.

L'outil apt (annotation processing tool) permet un traitement des annotations personnalisées durant la phase de compilation (compile time). L'outil apt permet la génération de nouveaux fichiers mais ne permet pas de modifier le code existant.

Il est important de se souvenir que lors du traitement des annotations le code source est parcouru mais il n'est pas possible de modifier ce code.

L'API reflexion est enrichie pour permettre de traiter les annotations lors de la phase d'exécution (runtime).

Java 6 intègre deux JSR concernant les annotations :

  • Pluggable Annotation Processing API (JSR 269)
  • Common Annotations (JSR 250)

L'Api Pluggable Annotation Processing permet d'intégrer le traitement des annotations dans le processus de compilation du compilateur Java ce qui évite d'avoir à utiliser apt.

Les annotations vont évoluer dans la plate-forme Java notamment au travers de plusieurs JSR qui sont en cours de définition :

  • JSR 305 Annotations for Software Defect Detection
  • JSR 308 Annotations on Java Types : doit permettre de mettre en oeuvre les annotations sur tous les types notamment les generics et sur les variables locales à l'exécution.

 

9.2. La mise en oeuvre des annotations

Les annotations fournissent des informations sur des entités : elles n'ont pas d'effets directs sur les entités qu'elles concernent.

Les annotations utilisent leur propre syntaxe. Une annotation s'utilise avec le caractère @ suivi du nom de l'annotation : elle doit obligatoirement précéder l'entité qu'elle annote. Par convention, les annotations s'utilisent sur une ligne dédiée.

Les annotations peuvent s'utiliser sur les packages, les classes, les interfaces, les méthodes, les constructeurs et les paramètres de méthodes.

Exemple :
@Override
public void maMethode() {
}

Une annotation peut avoir un ou plusieurs attributs : ceux-ci sont précisés entre parenthèses, séparés par une virgule. Un attribut est de la forme clé=valeur.

Exemple :
@SuppressWarnings(value = "unchecked")
void maMethode() { }

Lorsque l'annotation ne possède qu'un seul attribut, il est possible d'omettre son nom.

Exemple :
@SuppressWarnings("unchecked")
void maMethode() { }

Un attribut peut être de type tableau : dans ce cas, les différentes valeurs sont fournies entre accolades, chaque valeur placée entre guillemets et séparée de la suivante par une virgule.

Exemple :
@SuppressWarnings(value={"unchecked", "deprecation"})

Le tableau peut contenir des annotations.

Exemple :
@TODOItems({
  @Todo(importance = Importance.MAJEUR, 
      description = "Ajouter le traitement des erreurs", 
      assigneA = "JMD", 
      dateAssignation = "07-11-2007"),
  @Todo(importance = Importance.MINEURE, 
      description = "Changer la couleur de fond", 
      assigneA = "JMD", 
      dateAssignation = "13-12-2007")
})

 

9.3. L'utilisation des annotations

Les annotations prennent une place de plus en plus importante dans la plate-forme Java et dans de nombreuses API open source.

Les utilisations des annotations concernent plusieurs fonctionnalités :

  • Utilisation par le compilateur pour détecter des erreurs ou ignorer des avertissements
  • Documentation
  • Génération de code
  • Génération de fichiers

 

9.3.1. La documentation

Les annotations peuvent être mises en oeuvre pour permettre la génération de documentations indépendantes de JavaDoc : listes de choses à faire, de services ou de composants, ...

Il peut par exemple être pratique de rassembler certaines informations mises sous la forme de commentaires dans des annotations pour permettre leur traitement.

Par exemple, il est possible de définir une annotation qui va contenir les métadonnées relatives aux informations sur une classe. Traditionnellement, une classe débute par un commentaire d'en-tête qui contient des informations sur l'auteur, la date de création, les modifications, ... L'idée est de fournir ces informations dans une annotation dédiée. L'avantage est de facilement extraire et manipuler ces données qui ne seraient qu'informatives sous leur forme de commentaires.

 

9.3.2. L'utilisation par le compilateur

Les trois annotations fournies en standard avec la plate-forme entrent dans cette catégorie qui consiste à faire réaliser par le compilateur quelques contrôles basiques.

 

9.3.3. La génération de code

Les annotations sont particulièrement adaptées à la génération de code source afin de faciliter le travail des développeurs notamment sur des tâches répétitives.

Attention, le traitement des annotations ne peut pas modifier le code existant mais simplement créer de nouveaux fichiers sources.

 

9.3.4. La génération de fichiers

Les API standard ou les frameworks open source nécessitent fréquemment l'utilisation de fichiers de configuration ou de déploiement généralement au format XML.

Les annotations peuvent proposer une solution pour maintenir le contenu de ces fichiers par rapport aux entités incluses dans le code de l'application.

La version 5 de Java EE fait un important usage des annotations dans le but de simplifier les développements de certains composants notamment les EJB, les entités et les services web. Pour cela, l'utilisation de descripteurs est remplacée par l'utilisation d'annotations ce qui rend le code plus facile à développer et plus clair.

 

9.3.5. Les API qui utilisent les annotations

De nombreuses API standard utilisent les annotations depuis leur intégration dans Java notamment :

  • JAXB 2.0 : JSR 222 (Java Architecture for XML Binding 2.0)
  • Les services web de Java 6 (JAX-WS) : JSR 181 (Web Services Metadata for the Java Platform) et JSR 224 (Java APIs for XML Web Services 2.0 API)
  • Les EJB 3.0 et JPA : JSR 220 (Enterprise JavaBeans 3.0)
  • Servlets 3.0, CDI
  • ...

De nombreuses API open source utilisent aussi les annotations notamment JUnit, TestNG, Hibernate, ...

 

9.4. Les annotations standard

Java 5 propose plusieurs annotations standard.

 

9.4.1. L'annotation @Deprecated

Cette annotation a un rôle similaire au tag de même nom de Javadoc.

C'est un marqueur qui précise que l'entité concernée est obsolète et qu'il ne faudrait plus l'utiliser. Elle peut être utilisée avec une classe, une interface ou un membre (méthode ou champ)

Exemple :
public class TestDeprecated {

  public static void main(String[] args) {
    MaSousClasse td = new MaSousClasse();
    td.maMethode();
  }
}

@Deprecated
class MaSousClasse {

  /**
   * Afficher un message de test
   * @deprecated methode non compatible
   */
  @Deprecated 
  public void maMethode() {
    System.out.println("test");
  }  
}

Les entités marquées avec l'annotation @Deprecated devraient être documentées avec le tag @deprecated de Javadoc en lui fournissant la raison de l'obsolescence et éventuellement l'entité de substitution.

Il est important de tenir compte de la casse : @Deprecated pour l'annotation et @deprecated pour Javadoc.

Lors de la compilation, le compilateur donne une information si une entité obsolète est utilisée.

Exemple :
C:\Documents and Settings\jmd\workspace\Tests>javac TestDeprecated.java
Note: TestDeprecated.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

L'option -Xlint :deprecation permet d'afficher le détail sur les utilisations obsolètes.

Exemple :
C:\Documents and Settings\jmd\workspace\Tests>javac -Xlint:deprecation TestDepre
cated.java
TestDeprecated.java:7: warning: [deprecation] MaSousClasse in unnamed package ha
s been deprecated
    MaSousClasse td = new MaSousClasse();
    ^
TestDeprecated.java:7: warning: [deprecation] MaSousClasse in unnamed package ha
s been deprecated
    MaSousClasse td = new MaSousClasse();
                          ^
TestDeprecated.java:8: warning: [deprecation] maMethode() in MaSousClasse has be
en deprecated
    td.maMethode();
      ^
3 warnings

Il est aussi possible d'utiliser l'option -deprecation de l'outil javac.

 

9.4.2. L'annotation @Override

Cette annotation est un marqueur utilisé par le compilateur pour vérifier la réécriture de méthodes héritées.

@Override s'utilise pour annoter une méthode qui est une réécriture d'une méthode héritée. Le compilateur lève une erreur si aucune méthode héritée ne correspond.

Exemple :
@Override
public void maMethode() {
}

Son utilisation n'est pas obligatoire mais recommandée car elle permet de détecter certains problèmes.

Exemple :
public class MaClasseMere {
}

class MaClasse extends MaClasseMere {
  
  @Override
  public void maMethode() {
  }
}

Ceci est particulièrement utile pour éviter des erreurs de saisie dans le nom des méthodes à redéfinir.

Exemple :
public class TestOverride { 
  private String nom;
  private long id;
  
  public int hasCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + (int) (id ^ (id >>> 32));
    result = PRIME * result + ((nom == null) ? 0 : nom.hashCode());
    return result;
  }
}

Dans l'exemple ci-dessous, le développeur souhaitait redéfinir la méthode hashCode() mais suite à une faute frappe a simplement défini une nouvelle méthode nommée hasCode(). Cette classe se compile parfaitement mais elle comporte une erreur qui est signalée en utilisant l'annotation @Override.

Exemple :
public class TestOverride { 
  private String nom;
  private long id;
  
  @Override
  public int hasCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + (int) (id ^ (id >>> 32));
    result = PRIME * result + ((nom == null) ? 0 : nom.hashCode());
    return result;
  }
}


Résultat :
C:\Documents and Settings\jmd\workspace\Tests>javac TestOverride.java
TestOverride.java:6: method does not override or implement a method from a super
type
  @Override
  ^
1 error

 

9.4.3. L'annotation @SuppressWarning

L'annotation @SuppressWarning permet de demander au compilateur d'inhiber certains avertissements qui sont pris en compte par défaut.

La liste des avertissements utilisables dépend du compilateur. Un avertissement utilisé dans l'annotation non reconnu par le compilateur ne provoque par d'erreur mais éventuellement un avertissement.

Le compilateur fourni avec le JDK supporte les avertissements suivants :

Nom

Rôle

deprecation

Vérification de l'utilisation d'entités déclarées deprecated

unchecked

Vérification de l'utilisation des generics

fallthrough

Vérification de l'utilisation de l'instruction break dans les cases des instructions switch

path

Vérification des chemins fournis en paramètre du compilateur

serial

Vérification de la définition de la variable serialVersionUID dans les beans

finally

Vérification de l'absence d'instruction return dans une clause finally


Il est possible de passer en paramètres plusieurs types d'avertissements sous la forme d'un tableau

Exemple :
@SuppressWarnings(value={"unchecked", "fallthrough"})

L'exemple ci-dessous génère un avertissement à la compilation

Exemple :
import java.util.ArrayList;
import java.util.List;
 
public class TestSuppresWarning {
  public static void main(String[] args) {
    List donnees = new ArrayList();
    donnees.add("valeur1");
  }
}


Résultat :
C:\Documents and Settings\jmd\workspace\Tests>javac TestSuppresWarning.java
Note: TestSuppresWarning.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

L'option -Xlint :unchecked permet d'obtenir des détails

Exemple :
C:\Documents and Settings\jmd\workspace\Tests>javac -Xlint:unchecked TestSuppres
Warning.java
TestSuppresWarning.java:8: warning: [unchecked] unchecked call to add(E) as a me
mber of the raw type java.util.List
    donnees.add("valeur1");
               ^
1 warning

Pour supprimer cet avertissement, il faut utiliser les générics dans la déclaration de la collection ou utiliser l'annotation SuppressWarning.

Exemple :
import java.util.ArrayList;
import java.util.List;
 
@SuppressWarnings("unchecked")
public class TestSuppresWarning {
  public static void main(String[] args) {
    List donnees = new ArrayList();
    donnees.add("valeur1");
  }
}

Il n'est pas recommandé d'utiliser cette annotation mais plutôt d'apporter une solution à l'avertissement.

 

9.5. Les annotations communes (Common Annotations)

Les annotations communes sont définies par la JSR 250 et sont intégrées dans Java 6. Leur but est de définir des annotations couramment utilisées et ainsi d'éviter leur redéfinition pour chaque outil qui en aurait besoin.

Les annotations définies concernent :

  • la plate-forme standard dans le package javax.annotation (@Generated, @PostConstruct, @PreDestroy, @Resource, @Resources)
  • la plate-forme entreprise dans le package javax.annotation.security (@DeclareRoles, @DenyAll, @PermitAll, @RolesAllowed, @RunAs).

 

9.5.1. L'annotation @Generated

De plus en plus d'outils ou de frameworks génèrent du code source pour faciliter la tâche des développeurs notamment pour des portions de code répétitives ayant peu de valeur ajoutée.

Le code ainsi généré peut être marqué avec l'annotation @Generated.

Exemple :
    @Generated(
        value = "entite.qui.a.genere.le.code", 
        comments = "commentaires", 
        date = "12 April  2008"
    )
    public void toolGeneratedCode(){
    }

L'attribut obligatoire value permet de préciser l'outil à l'origine de la génération

Les attributs facultatifs comments et date permettent respectivement de fournir un commentaire et la date de génération.

Cette annotation peut être utilisée sur toutes les déclarations d'entités.

 

9.5.2. Les annotations @Resource et @Resources

L'annotation @Resource définit une ressource requise par une classe. Typiquement, une ressource est par exemple un composant Java EE de type EJB ou JMS.

L'annotation @Resource possède plusieurs attributs :

Attribut

Description

authentificationType

Type d'authentification pour utiliser la ressource (Resource.AuthenticationType.CONTAINER ou Resource.AuthenticationType.APPLICATION)

description

Description de la ressource

mappedName

Nom de la ressource spécifique au serveur utilisé (non portable)

name

Nom JNDI de la ressource

shareable

Booléen qui précise si la ressource est partagée

type

Le type pleinement qualifié de la ressource


Cette annotation peut être utilisée sur une classe, un champ ou une méthode.

Lorsque l'annotation est utilisée sur une classe, elle correspond simplement à une déclaration des ressources qui seront requises à l'exécution.

Lorsque l'annotation est utilisée sur un champ ou une méthode, le serveur d'applications va injecter une référence sur la ressource correspondante. Pour cela, lors du chargement d'une application par le serveur d'applications, celui-ci recherche les annotations @Resource afin d'assigner une instance de la ressource correspondante.

Exemple :
@Resource(name="MaQueue", 
    type = "javax.jms.Queue", 
    shareable=false, 
    authenticationType=Resource.AuthenticationType.CONTAINER, 
    description="Queue de test"
)
private javax.jms.Queue maQueue;

L'annotation @Resources est simplement une collection d'annotation de type @Resource.

Exemple :
@Resources({
    @Resource(name = "maQueue" type = javax.jms.Queue),
    @Resource(name = "monTopic" type = javax.jms.Topic),
})

 

9.5.3. Les annotations @PostConstruct et @PreDestroy

Les annotations @PostConstruct et @PreDestroy permettent respectivement de désigner des méthodes qui seront exécutées après l'instanciation d'un objet et avant la destruction d'une instance.

Ces deux annotations ne peuvent être utilisées que sur des méthodes.

Ces annotations sont par exemple utiles dans Java EE car généralement un composant géré par le conteneur est instancié en utilisant le constructeur sans paramètre. Une méthode marquée avec l'annotation @PostConstruct peut alors être exécutée juste après l'appel au constructeur.

Une telle méthode doit respecter certaines règles :

  • ne pas avoir de paramètres sauf dans des cas précis (exemple avec les intercepteurs des EJB)
  • ne pas avoir de valeur de retour (elle doit renvoyer void)
  • ne doit pas lever d'exceptions vérifiées
  • ne doit pas être statique

L'annotation @PostConstruct est utilisée en général sur une méthode qui initialise des ressources en fonction du contexte.

Dans une même classe, chacune de ces annotations n'est utilisable que par une seule méthode.

 

9.6. Les annotations personnalisées

Java propose la possibilité de définir ses propres annotations. Pour cela, le langage possède un type dédié : le type d'annotation (annotation type).

Un type d'annotation est similaire à une classe et une annotation est similaire à une instance de classe.

 

9.6.1. La définition d'une annotation

Sur la plate-forme Java, une annotation est une interface lors de sa déclaration et est une instance d'une classe qui implémente cette interface lors de son utilisation.

La définition d'une annotation nécessite une syntaxe particulière utilisant le mot clé @interface. Une annotation se déclare donc de façon similaire à une interface.

Exemple : le fichier MonAnnotation.java
package com.jmdoudoux.test.annotations;
 
public @interface MonAnnotation {
 
}

Une fois compilée, cette annotation peut être utilisée dans le code. Pour utiliser une annotation, il faut importer l'annotation et l'appeler dans le code en la faisant précéder du caractère @.

Exemple :
package com.jmdoudoux.test.annotations;
 
@MonAnnotation
public class MaCLasse {
 
}

Si l'annotation est définie dans un autre package, il faut utiliser la syntaxe pleinement qualifiée du nom de l'annotation ou ajouter une clause import pour le package.

Il est possible d'ajouter des membres à l'annotation simplement en définissant une méthode dont le nom correspond au nom de l'attribut en paramètre de l'annotation.

Exemple :
package com.jmdoudoux.test.annotations;
 
public @interface MonAnnotation {
  String arg1();
  String arg2();
}
 
package com.jmdoudoux.test.annotations;
 
@MonAnnotation(arg1="valeur1", arg2="valeur2")
public class MaCLasse {
 
}

Les types utilisables sont les chaînes de caractères, les types primitifs, les énumérations, les annotations, les chaînes de caractères, le type Class.

Il est possible de définir un membre comme étant un tableau à une seule dimension d'un des types utilisables.

Exemple :
package com.jmdoudoux.tests.annotations
 
public @interface MonAnnotation {
    String arg1();
    String[] arg2();
    String arg3();
}

Il est possible de définir une valeur par défaut, ce qui rend l'indication du membre optionnelle. Cette valeur est précisée en la faisant précéder du mot clé default.

Exemple :
package com.jmdoudoux.test.annotations;
 
public @interface MonAnnotation {
  String arg1() default "";
  String[] arg2();
  String arg3();
}

La valeur par défaut d'un tableau utilise une syntaxe raccourcie.

Exemple :
package com.jmdoudoux.tests.annotations
 
public @interface MonAnnotation {
    String arg1();
    String[] arg2() default {"chaine1", "chaine2" };
    String arg3();
}

Il est possible de définir une énumération comme type pour un attribut

Exemple :
package com.jmdoudoux.test.annotations;
 
public @interface MonAnnotation {
  public enum Niveau {DEBUTANT, CONFIRME, EXPERT} ;
  String arg1() default "";
  String[] arg2();
  String arg3();
  Niveau niveau() default Niveau.DEBUTANT;
 
}

 

9.6.2. Les annotations pour les annotations

La version 5 de Java propose quatre annotations dédiées aux types d'annotations qui permettent de fournir des informations sur l'utilisation.

Ces annotations sont définies dans le package java.lang.annotation

 

9.6.2.1. L'annotation @Target

L'annotation @Target permet de préciser les entités sur lesquelles l'annotation sera utilisable. Cette annotation attend comme valeur un tableau de valeurs issues de l'énumération ElementType.

Valeur de l'énumération

Rôle

ANNOTATION_TYPE

Types d'annotation

CONSTRUCTOR

Constructeurs

LOCAL_VARIABLE

Variables locales

FIELD

Champs

METHOD

Méthodes hors constructeurs

PACKAGE

Packages

PARAMETER

Paramètres d'une méthode ou d'un constructeur

TYPE

Classes, interfaces, énumérations, types d'annotation


Si une annotation est utilisée sur une entité non précisée par l'annotation, alors une erreur est émise lors de la compilation.

Exemple :
package com.jmdoudoux.test.annotations;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
 
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface MonAnnotation {
  String arg1() default "";
  String arg2();
}
 
package com.jmdoudoux.test.annotations;
 
@MonAnnotation(arg1="valeur1", arg2="valeur2")
public class MaCLasse {
 
}


Résultat de la compilation :
C:\Documents and Settings\jmd\workspace\Tests>javac com/jmdoudoux/test/annotatio
ns/MaClasse.java
com\jmdoudoux\test\annotations\MaClasse.java:3: annotation type not applicable t
o this kind of declaration
@MonAnnotation(arg1="valeur1", arg2="valeur2")
^
1 error

 

9.6.2.2. L'annotation @Retention

Cette annotation permet de préciser à quel niveau les informations concernant l'annotation seront conservées. Cette annotation attend comme valeur un élément de l'énumération RetentionPolicy.

Enumération

Rôle

RetentionPolicy.SOURCE

informations conservées dans le code source uniquement (fichier .java) : le compilateur les ignore

RetentionPolicy.CLASS

informations conservées dans le code source et le bytecode (fichiers .java et .class)

RetentionPolicy.RUNTIME

informations conservées dans le code source et le bytecode : elles sont disponibles à l'exécution par introspection


Cette annotation permet de déterminer de quelle façon l'annotation pourra être exploitée.

Exemple :
@Retention(RetentionPolicy.RUNTIME)

 

9.6.2.3. L'annotation @Documented

L'annotation @Documented permet de demander l'intégration de l'annotation dans la documentation générée par Javadoc.

Par défaut, les annotations ne sont pas intégrées dans la documentation des classes annotées.

Exemple :
package com.jmdoudoux.test.annotations;
 
import java.lang.annotation.Documented;
 
@Documented
public @interface MonAnnotation {
  String arg1() default "";
  String arg2();
}

 

9.6.2.4. L'annotation @Inherited

L'annotation @Inherited permet de demander l'héritage d'une annotation aux classes filles de la classe mère sur laquelle elle s'applique.

Si une classe mère est annotée avec une annotation elle-même annotée avec @Inherited alors toutes les classes filles sont automatiquement annotées avec cette annotation.

 

9.7. L'exploitation des annotations

Pour être profitables, les annotations ajoutées dans le code source doivent être exploitées par un ou plusieurs outils.

La déclaration et l'utilisation d'annotations sont relativement simples par contre leur exploitation pour permettre la production de fichiers est moins triviale.

Cette exploitation peut se faire de plusieurs manières

  • en définissant un doclet qui exploite le code source
  • en utilisant apt au moment de la compilation
  • en utilisant l'introspection lors de l'exécution
  • en utilisant le compilateur java à partir de Java 6.0

 

9.7.1. L'exploitation des annotations dans un Doclet

Pour des traitements simples, il est possible de définir un Doclet et de le traiter avec l'outil Javadoc pour utiliser les annotations.

L'API Doclet est définit dans le package com.sun.javadoc. Ce package est dans le fichier tools.jar fourni avec le JDK.

L'API Doclet définit des interfaces pour chaque entité pouvant être utilisée dans le code source.

La méthode annotation() de l'interface ProgramElementDoc permet d'obtenir un tableau de type AnnotationDesc.

L'interface AnnotationDesc représente une annotation. Elle définit deux méthodes :

Méthode

Rôle

AnnotationTypeDoc annotationType()

Renvoyer le type d'annotation

AnnotationDesc.ElementValuePair[] elementValues()

Renvoyer les éléments de l'annotation


L'interface AnnotationTypeDoc représente un type d'annotation. Elle ne définit qu'une seule méthode :

Méthode

Rôle

AnnotationTypeElementDoc[] elements()

Renvoyer les éléments d'un type d'annotation


L'interface AnnotationTypeElementDoc représente un élément d'un type d'annotation. Elle ne définit qu'une seule méthode :

Méthode

Rôle

AnnotationValue defaultValue()

Renvoyer la valeur par défaut de l'élément d'un type d'annotation


L'interface AnnotationValue représente la valeur d'un élément d'un type d'annotation. Elle définit deux méthodes :

Méthode

Rôle

String toString()

Renvoyer la valeur sous forme d'une chaîne de caractères

Object value()

Renvoyer la valeur


Pour créer un Doclet, il faut définir une classe qui contienne une méthode ayant pour signature public static boolean start (RootDoc rootDoc).

Pour utiliser le Doclet, il faut compiler la classe qui l'encapsule et utiliser l'outil javadoc avec l'option -doclet suivi du nom de la classe.

 

9.7.2. L'exploitation des annotations avec l'outil Apt

La version 5 du JDK fournit l'outil apt pour le traitement des annotations.

L'outil apt qui signifie annotation processing tool est l'outil le plus polyvalent en Java 5 pour exploiter les annotations.

Apt assure la compilation des classes et permet en simultané le traitement des annotations par des processeurs d'annotations créés par le développeur.

Les processeurs d'annotations peuvent générer de nouveaux fichiers sources, pouvant eux-mêmes contenir des annotations. Apt traite alors récursivement les fichiers générés jusqu'à ce qu'il n'y ait plus d'annotation à traiter et de classe à compiler.

Cette section va créer un processeur pour l'annotation personnalisée Todo

Exemple : l'annotation personnalisée Todo
package com.jmdoudoux.test.annotations;
 
import java.lang.annotation.Documented;
 
@Documented
public @interface Todo {
 
  public enum Importance {
    MINEURE, IMPORTANT, MAJEUR, CRITIQUE
  };
 
  Importance importance() default Importance.MINEURE;
 
  String[] description();
 
  String assigneA();
 
  String dateAssignation();
}

Apt et l'API à utiliser de concert ne sont disponibles qu'avec le JDK : ils ne sont pas fournis avec le JRE.

Les packages de l'API sont dans le fichier lib/tools.jar du JDK : cette bibliothèque doit donc être ajoutée au classpath lors de la mise en oeuvre d'apt.

L'API est composée de deux grandes parties :

  • Modélisation du langage
  • Interaction avec l'outil de traitement des annotations

L'API est contenue dans plusieurs sous-packages de com.sun.mirror notamment :

  • com.sun.mirror.apt : contient les interfaces pour la mise en oeuvre d'apt
  • com.sun.mirror.declaration : encapsule la déclaration des entités dans les sources qui peuvent être annotées (packages, classes, méthodes, ... ) sous le forme d'interfaces qui héritent de l'interface Declaration
  • com.sun.mirror.type : encapsule les types d'entités dans les sources sous la forme d'interfaces qui héritent de l'interface TypeMirror
  • com.sun.mirror.util : propose des utilitaires

Un processeur d'annotations est une classe qui implémente l'interface com.sun.mirror.apt.AnnotationProcessor. Cette interface ne définit qu'une seule méthode process() qui va contenir les traitements à réaliser pour une annotation.

Il faut fournir un constructeur qui attend en paramètre un objet de type com.sun.mirror.apt.AnnotationProcessorEnvironment : ce constructeur sera appelé par une fabrique pour en créer une instance.

L'interface AnnotationProcessorEnvironment fournit des méthodes pour obtenir des informations sur l'environnement d'exécution des traitements des annotations et créer de nouveaux fichiers pendant les traitements.

L'interface Declaration permet d'obtenir des informations sur une entité :

Méthode

Rôle

<A extends Annotation> getAnnotation(Class<A> annotationType)

Renvoie une annotation d'un certain type associée à l'entité

Collection<AnnotationMirror> getAnnotationMirrors()

Renvoie les annotations associées à l'entité

String getDocComment()

Renvoie le texte des commentaires de documentations Javadoc associés à l'entité

Collection<Modifier> getModifiers()

Renvoie les modificateurs de l'entité

SourcePosition getPosition()

Renvoie la position de la déclaration dans le code source

String getSimpleName()

Renvoie le nom de la déclaration


De nombreuses interfaces héritent de l'interface Declaration : AnnotationTypeDeclaration, AnnotationTypeElementDeclaration, ClassDeclaration, ConstructorDeclaration, EnumConstantDeclaration, EnumDeclaration, ExecutableDeclaration, FieldDeclaration, InterfaceDeclaration, MemberDeclaration, MethodDeclaration, PackageDeclaration, ParameterDeclaration, TypeDeclaration, TypeParameterDeclaration

Chacune de ces interfaces propose des méthodes pour obtenir des informations sur la déclaration et sur le type concernés.

L'interface TypeMirror permet d'obtenir des informations sur un type utilisé dans une déclaration.

De nombreuses interfaces héritent de l'interface TypeMirror : AnnotationType, ArrayType, ClassType, DeclaredType, EnumType, InterfaceType, PrimitiveType, ReferenceType, TypeVariable, VoidType, WildcardType.

La classe com.sun.mirror.util.DeclarationFilter permet de définir un filtre des entités annotées avec les annotations concernées par les traitements du processeur. Il suffit de créer une instance de cette classe en ayant redéfini sa méthode match(). Cette méthode renvoie un booléen qui précise si l'entité fournie en paramètre sous la forme d'un objet de type Declaration est annotée avec une des annotations concernées par le processeur.

Exemple :
package com.jmdoudoux.test.annotations.outils;
 
import java.util.Collection;
 
import com.jmdoudoux.test.annotations.Todo;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.Declaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.DeclarationFilter;
 
public class TodoAnnotationProcessor implements AnnotationProcessor {
  private final AnnotationProcessorEnvironment env;
 
  public TodoAnnotationProcessor(AnnotationProcessorEnvironment env) {
    this.env = env;
  }
 
  public void process() {
    // Création d'un filtre pour ne retenir que les déclarations annotées avec Todo
    DeclarationFilter annFilter = new DeclarationFilter() {
      public boolean matches(
          Declaration d) {
        return d.getAnnotation(Todo.class) != null;
      }
    };
 
    // Recherche des entités annotées avec Todo
    Collection<TypeDeclaration> types = annFilter.filter(env.getSpecifiedTypeDeclarations());
    for (TypeDeclaration typeDecl : types) {
      System.out.println("class name: " + typeDecl.getSimpleName());
 
      Todo todo = typeDecl.getAnnotation(Todo.class);
 
      System.out.println("description : ");
      for (String desc : todo.description()) {
        System.out.println(desc);
      }
      System.out.println("");
    }
  }
}

Il faut créer une fabrique de processeurs d'annotations : cette fabrique est en charge d'instancier des processeurs d'annotations pour un ou plusieurs types d'annotations. La fabrique doit implémenter l'interface com.sun.mirror.apt.AnnotationProcessorFactory.

L'interface AnnotationProcessorFactory déclare trois méthodes :

Méthode

Rôle

AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env)

Renvoyer un processeur d'annotations pour l'ensemble de types d'annotations fournis en paramètres.

Collection<String> supportedAnnotationTypes()

Renvoyer une collection des types d'annotations dont un processeur peut être instancié par la fabrique

Collection<String> supportedOptions()

Renvoyer une collection des options supportées par la fabrique ou par les processeurs d'annotations créés par la fabrique


Exemple :
package com.jmdoudoux.test.annotations.outils;
 
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
 
import java.util.Collection;
import java.util.Set;
import java.util.Collections;
import java.util.Arrays;
 
public class TodoAnnotationProcessorFactory implements AnnotationProcessorFactory {
  private static final Collection<String> supportedAnnotations = 
    Collections.unmodifiableCollection(Arrays
      .asList("com.jmdoudoux.test.annotations.Todo"));
 
  private static final Collection<String> supportedOptions     = Collections.emptySet();
 
  public Collection<String> supportedOptions() {
    return supportedOptions;
  }
 
  public Collection<String> supportedAnnotationTypes() {
    return supportedAnnotations;
  }
 
  public AnnotationProcessor getProcessorFor(
      Set<AnnotationTypeDeclaration> atds,
      AnnotationProcessorEnvironment env) {
    return new TodoAnnotationProcessor(env);
  }
}

Dans l'exemple ci-dessus, aucune option n'est supportée et la fabrique ne prend en charge que l'annotation personnalisée Todo.

Pour mettre en oeuvre les traitements des annotations, il faut que le code source utilise ces annotations.

Exemple : une classe annotée avec l'annotation Todo
package com.jmdoudoux.test;
 
import com.jmdoudoux.test.annotations.Todo;
import com.jmdoudoux.test.annotations.Todo.Importance;
 
@Todo(importance = Importance.CRITIQUE, 
      description = "Corriger le bug dans le calcul", 
      assigneA = "JMD", 
      dateAssignation = "11-11-2007")
public class MaClasse {
 
}


Exemple : une autre classe annotée avec l'annotation Todo
package com.jmdoudoux.test;
 
import com.jmdoudoux.test.annotations.Todo;
import com.jmdoudoux.test.annotations.Todo.Importance;
 
@Todo(importance = Importance.MAJEUR, 
      description = "Ajouter le traitement des erreurs", 
      assigneA = "JMD", 
      dateAssignation = "07-11-2007")
public class MaClasse1 {
 
}

Pour utiliser apt, il faut que le classpath contienne la bibliothèque tools.jar fournie avec le JDK et les classes de traitements des annotations (fabrique et processeur d'annotations).

L'option -factory permet de préciser la fabrique à utiliser.

Résultat de l'exécution d'apt
C:\Documents and Settings\jmd\workspace\Tests>apt -cp ".;./bin;C:/Program Files/
Java/jdk1.5.0_07/lib/tools.jar" -factory com.jmdoudoux.test.annotations.outils.T
odoAnnotationProcessorFactory com/jmdoudoux/test/*.java
class name: MaClasse
description :
Corriger le bug dans le calcul
 
class name: MaClasse1
description :
Ajouter le traitement des erreurs

A partir de l'objet de type AnnotationProcessorEnvironment, il est possible d'obtenir un objet de type com.sun.miror.apt.Filer qui encapsule un nouveau fichier créé par le processeur d'annotations.

L'interface Filer propose quatre méthodes pour créer différents types de fichiers :

Méthode

Rôle

OutputStream createBinaryFile(Filer.Location loc, String pkg, File relPath)

Créer un nouveau fichier binaire et renvoyer un objet de type Stream pour écrire son contenu

OutputStream createClassFile(String name)

Créer un nouveau fichier .class et renvoyer un objet de type Stream pour écrire son contenu

PrintWriter createSourceFile(String name)

Créer un nouveau fichier texte contenant du code source et renvoyer un objet de type Writer pour écrire son contenu

PrintWriter createTextFile(Filer.Location loc, String pkg, File relPath, String charsetName)

Créer un nouveau fichier texte et renvoyer un objet de type Writer pour écrire son contenu


L'énumération Filter.Location permet de préciser si le nouveau fichier est créé dans la branche source (SOURCE_TREE) ou dans la branche compilée (CLASS_TREE).

Exemple :
package com.jmdoudoux.test.annotations.outils;
 
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
 
import com.jmdoudoux.test.annotations.Todo;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.Filer;
import com.sun.mirror.declaration.Declaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.DeclarationFilter;
 
public class TodoAnnotationProcessor implements AnnotationProcessor {
  private final AnnotationProcessorEnvironment env;
 
  public TodoAnnotationProcessor(AnnotationProcessorEnvironment env) {
    this.env = env;
  }
 
  public void process() {
    // Création d'un filtre pour ne retenir que les déclarations annotées avec
    // Todo
    DeclarationFilter annFilter = new DeclarationFilter() {
      public boolean matches(
          Declaration d) {
        return d.getAnnotation(Todo.class) != null;
      }
    };
 
    Filer f = this.env.getFiler();
    PrintWriter out;
    try {
      out = f.createTextFile(Filer.Location.SOURCE_TREE, "", new File("todo.txt"), null);
 
      // Recherche des entités annotées avec Todo
      Collection<TypeDeclaration> types = annFilter.filter(env.getSpecifiedTypeDeclarations());
      for (TypeDeclaration typeDecl : types) {
        out.println("class name: " + typeDecl.getSimpleName());
 
        Todo todo = typeDecl.getAnnotation(Todo.class);
 
        out.println("description : ");
        for (String desc : todo.description()) {
          out.println(desc);
        }
        out.println("");
      }
 
      out.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}


Résultat de l'exécution
C:\Documents and Settings\jm\workspace\Tests>apt -cp ".;./bin;C:/Program Files/
Java/jdk1.5.0_07/lib/tools.jar" -factory com.jmdoudoux.test.annotations.outils.T
odoAnnotationProcessorFactory com/jmdoudoux/test/*.java
 
C:\Documents and Settings\jm\workspace\Tests>dir
 Volume in drive C has no label.
 Volume Serial Number is 1D31-4F67
 
 Directory of C:\Documents and Settings\jmd\workspace\Tests
 
19/11/2007  08:39    <DIR>          .
19/11/2007  08:39    <DIR>          ..
16/11/2007  08:15               433 .classpath
31/10/2006  14:06               381 .project
14/09/2007  12:45    <DIR>          .settings
16/11/2007  08:15    <DIR>          bin
02/10/2007  15:22               854 build.xml
29/06/2007  07:12    <DIR>          com
15/11/2007  13:01    <DIR>          doc
19/11/2007  08:39               148 todo.txt
              8 File(s)          1 812 bytes
               6 Dir(s)  66 885 595 136 bytes free
 
C:\Documents and Settings\jm\workspace\Tests>type todo.txt
class name: MaClasse
description :
Corriger le bug dans le calcul
 
class name: MaClasse1
description :
Ajouter le traitement des erreurs

Concernant les entités à traiter, l'API Mirror fournit de nombreuses autres fonctionnalités qui permettent de rendre très riche le traitement des annotations. Parmi ces fonctionnalités, il y a le parcours des sources par des classes mettant en oeuvre le motif de conception visiteur.

 

9.7.3. L'exploitation des annotations par introspection

Pour qu'une annotation soit exploitée à l'exécution, il est nécessaire qu'elle soit annotée avec une RetentionPolicy à la valeur RUNTIME.

Exemple :
package com.jmdoudoux.test.annotations;
 
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
 
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Todo {
 
  public enum Importance {
    MINEURE, IMPORTANT, MAJEUR, CRITIQUE
  };
 
  Importance importance() default Importance.MINEURE;
 
  String[] description();
 
  String assigneA();
 
  String dateAssignation();
}

L'interface java.lang.reflect.AnnotatedElement définit les méthodes pour le traitement des annotations par introspection :

Méthode

Rôle

<T extends Annotation> getAnnotation(Class<T>)

Renvoyer l'annotation si le type fourni en paramètre est utilisé sur l'entité, sinon null

Annotation[] getAnnotations()

Renvoyer un tableau de toutes les annotations de l'entité. Renvoie un tableau vide si aucune annotation n'est concernée

Annotation[] getDeclaredAnnotations()

Renvoyer un tableau des annotations directement associées à l'entité (en ignorant donc les annotations héritées). Renvoie un tableau vide si aucune annotation n'est concernée

boolean isAnnotationPresent(Class< ? extends Annotation>)

Renvoyer true si l'annotation dont le type est fourni en paramètre est utilisé sur l'entité. Cette méthode est particulièrement utile dans le traitement des annotations de type marqueur.


Plusieurs classes du package java.lang implémentent l'interface AnnotatedElement : AccessibleObject, Class, Constructor, Field, Method et Package

Exemple :
package com.jmdoudoux.test;
 
import java.lang.reflect.Method;
 
import com.jmdoudoux.test.annotations.Todo;
import com.jmdoudoux.test.annotations.Todo.Importance;
 
@Todo(importance = Importance.CRITIQUE, 
      description = "Corriger le bug dans le calcul", 
      assigneA = "JMD", 
      dateAssignation = "11-11-2007")
public class TestInstrospectionAnnotation {
 
  public static void main(
      String[] args) {
    Todo todo = null;
 
    // traitement annotation sur la classe
    Class classe = TestInstrospectionAnnotation.class;
    todo = (Todo) classe.getAnnotation(Todo.class);
    if (todo != null) {
      System.out.println("classe "+classe.getName());
      System.out.println("  ["+todo.importance()+"]"+" ("+todo.assigneA()
	    +" le "+todo.dateAssignation()+")");
      for(String desc : todo.description()) {
        System.out.println("     _ "+desc);
      }
    }
    
    // traitement annotation sur les méthodes de la classe    
    for(Method m : TestInstrospectionAnnotation.class.getMethods()) {
      todo = (Todo) m.getAnnotation(Todo.class);
      if (todo != null) {
        System.out.println("méthode "+m.getName());
        System.out.println("  ["+todo.importance()+"]"+" ("+todo.assigneA()
		  +" le "+todo.dateAssignation()+")");
        for(String desc : todo.description()) {
          System.out.println("     _ "+desc);
        }
      }
    }
  }
 
  @Todo(importance = Importance.MAJEUR, 
        description = "Implémenter la methode", 
        assigneA = "JMD", 
        dateAssignation = "11-11-2007")
  public void methode1() {
    
  }
  
  @Todo(importance = Importance.MINEURE, 
        description = {"Compléter la methode", "Améliorer les logs"}, 
        assigneA = "JMD", 
        dateAssignation = "12-11-2007")
  public void methode2() {
    
  }
 
}


Résultat d'exécution :
classe com.jmdoudoux.test.TestInstrospectionAnnotation
  [CRITIQUE] (JMD le 11-11-2007)
     _ Corriger le bug dans le calcul
méthode methode1
  [MAJEUR] (JMD le 11-11-2007)
     _ Implémenter la methode
méthode methode2
  [MINEURE] (JMD le 12-11-2007)
     _ Compléter la methode
     _ Améliorer les logs

Pour obtenir les annotations sur les paramètres d'un constructeur ou d'une méthode, il faut utiliser la méthode getParameterAnnotations() des classes Constructor ou Method qui renvoie un objet de type Annotation[][]. La première dimension du tableau concerne les paramètres dans leur ordre de déclaration. La seconde dimension contient les annotations de chaque paramètre.

 

9.7.4. L'exploitation des annotations par le compilateur Java

Dans la version 6 de Java SE, la prise en compte des annotations est intégrée dans le compilateur : ceci permet un traitement à la compilation des annotations sans avoir recours à un outil tiers comme apt.

Une nouvelle API a été définie par la JSR 269 (Pluggable annotations processing API) et ajoutée dans la package javax.annotation.processing.

Cette API est détaillée dans la section suivante.

 

9.8. L'API Pluggable Annotation Processing

La version 6 de Java apporte plusieurs améliorations dans le traitement des annotations notamment l'intégration de ces traitements directement dans le compilateur javac grâce à une nouvelle API dédiée.

L'API Pluggable Annotation Processing est définie dans la JSR 269. Elle permet un traitement des annotations directement par le compilateur en proposant une API aux développeurs pour traiter les annotations incluses dans le code source.

Apt et son API proposaient déjà une solution à ces traitements mais cette API standardise le traitement des annotations au moment de la compilation. Il n'est donc plus nécessaire d'utiliser un outil tiers post compilation pour traiter les annotations à la compilation.

Dans les exemples de cette section, les classes suivantes seront utilisées :

Exemple : MaClasse.java
package com.jmdoudoux.tests;
 
import com.jmdoudoux.tests.annotations.Todo;
import com.jmdoudoux.tests.annotations.Todo.Importance;
 
@Todo(importance = Importance.CRITIQUE, 
      description = "Corriger le bug dans le calcul", 
      assigneA = "JMD", 
      dateAssignation = "11-11-2007")
public class MaClasse {
 
}


Exemple : MaClasse1.java
package com.jmdoudoux.tests;
 
import com.jmdoudoux.tests.annotations.Todo;
import com.jmdoudoux.tests.annotations.Todo.Importance;
 
@Todo(importance = Importance.MAJEUR, 
      description = "Ajouter le traitement des erreurs", 
      assigneA = "JMD", 
      dateAssignation = "07-11-2007")
public class MaClasse1 {
 
}


Exemple : MaClasse3.java
package com.jmdoudoux.tests;
 
@Deprecated
public class MaClasse3 {
 
}

Un exemple de mise en oeuvre de l'API est aussi fourni avec le JDK dans le sous-répertoire sample/javac/processing.

 

9.8.1. Les processeurs d'annotations

La mise en oeuvre de cette API nécessite l'utilisation des packages javax.annotation.processing, javax.lang.model et javax.tools.

Un processeur d'annotations doit implémenter l'interface Processor. Le traitement des annotations se fait en plusieurs passes (round). A chaque passe le processeur est appelé pour traiter des classes qui peuvent avoir été générées lors de la précédente passe. Lors de la première passe, ce sont les classes fournies initialement qui sont traitées.

L'interface javax.annotation.processing.Processor définit les méthodes d'un processeur d'annotations. Pour définir un processeur, il est possible de créer une classe qui implémente l'interface Processor mais le plus simple est d'hériter de la classe abstraite javax.annotation.processing.AbstractProcessor.

La classe AbstractProcessor contient une variable nommée processingEnv de type ProcessingEnvironment. La classe ProcessingEnvironment permet d'obtenir des instances de classes qui permettent des interactions avec l'extérieur du processeur ou fournissent des utilitaires :

  • Filer : classe qui permet la création de fichiers
  • Messager : classe qui permet d'envoyer des messages affichés par le compilateur
  • Elements : classe qui fournit des utilitaires pour les éléments
  • Types : classe qui fournit des utilitaires pour les types

La méthode getRootElements() renvoie les classes Java qui seront traitées par le processeur dans cette passe.

La méthode la plus importante est la méthode process() : c'est elle qui va contenir les traitements exécutés par le processeur. Elle possède deux paramètres :

  • Un ensemble des annotations qui seront traitées par le processeur
  • Un objet qui encapsule l'étape courante des traitements

Deux annotations sont dédiées aux processeurs d'annotations et doivent être utilisées sur la classe du processeur :

  • @SupportedAnnotationTypes : cette annotation permet de préciser les types d'annotations traitées par le processeur. La valeur « * » permet d'indiquer que tous seront traités.
  • @SupportedSourceVersion : cette annotation permet de préciser la version du code source traité par le processeur
Exemple :
package com.jmdoudoux.tests.annotations.outils;
 
import java.util.Set;
 
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
 
import com.jmdoudoux.tests.annotations.Todo;
 
@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class TodoProcessor extends AbstractProcessor {
 
  @Override
  public boolean process(
      Set<? extends TypeElement> annotations,
      RoundEnvironment roundEnv) {
 
    Messager messager = processingEnv.getMessager();
 
    for (TypeElement te : annotations) {
      messager.printMessage(Kind.NOTE, "Traitement annotation " 
	    + te.getQualifiedName());
 
      for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
        messager.printMessage(Kind.NOTE, "  Traitement élément " 
		  + element.getSimpleName());
        Todo todo = element.getAnnotation(Todo.class);
 
        if (todo != null) {
          messager.printMessage(Kind.NOTE, "  affecté le " + todo.dateAssignation() 
		    + " a " + todo.assigneA());
        }
      }
    }
 
    return true;
  }
}

 

9.8.2. L'utilisation des processeurs par le compilateur

Le compilateur javac est enrichi avec plusieurs options concernant le traitement des annotations :

Option

Rôle

-processor

permet de préciser le nom pleinement qualifié du processeur à utiliser

-proc

vérifie si le traitement des annotations et/ou la compilation sont effectués

-processorpath

classpath des processeurs d'annotations

-A

permet de passer des options aux processeurs d'annotations sous la forme de paires cle=valeur

-XprintRounds

option non standard qui permet d'afficher des informations sur le traitement des annotations par les processeurs

-XprintProcessorInfo

option non standard qui affiche la liste des annotations qui seront traitées par les processeurs d'annotations


Le compilateur fait appel à la méthode process() du processeur en lui passant en paramètre l'ensemble des annotations trouvées par le compilateur dans le code source.

Résultat :
C:\Documents and Settings\jm\workspace\TestAnnotations>javac -cp ".;./bin;C:/Pr
ogram Files/Java/jdk1.6.0/lib/tools.jar" -processor com.jmdoudoux.tests.annotati
ons.outils.TodoProcessor com/jmdoudoux/tests/*.java
Note: Traitement annotation com.jmdoudoux.tests.annotations.Todo
Note:   Traitement élément MaClasse
Note:   affecte le 11-11-2007 a JMD
Note:   Traitement élément MaClasse1
Note:   affecte le 07-11-2007 a JMD
Note: Traitement annotation java.lang.Deprecated
Note:   Traitement élément MaClasse3

 

9.8.3. La création de nouveaux fichiers

La classe Filer permet de créer des fichiers lors du traitement des annotations.

Exemple :
package com.jmdoudoux.tests.annotations.outils;
 
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
 
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
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.tools.StandardLocation;
import javax.tools.Diagnostic.Kind;
 
import com.jmdoudoux.tests.annotations.Todo;
 
@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class TodoProcessor2 extends AbstractProcessor {
 
  @Override
  public boolean process(
      Set<? extends TypeElement> annotations,
      RoundEnvironment roundEnv) {
 
    Filer filer = processingEnv.getFiler();
    Messager messager = processingEnv.getMessager();
    Elements eltUtils = processingEnv.getElementUtils();
    if (!roundEnv.processingOver()) {
      TypeElement elementTodo = 
	    eltUtils.getTypeElement("com.jmdoudoux.tests.annotations.Todo");
      Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(elementTodo);
      if (!elements.isEmpty())
        try {
          messager.printMessage(Kind.NOTE, "Création du fichier Todo");
          PrintWriter pw = new PrintWriter(filer.createResource(
		      StandardLocation.SOURCE_OUTPUT, "", "Todo.txt")
              .openOutputStream());
          // .createSourceFile("Todo").openOutputStream());
          pw.println("Liste des todos\n");
 
          for (Element element : elements) {
            pw.println("\nélément:" + element.getSimpleName());
            Todo todo = (Todo) element.getAnnotation(Todo.class);
            pw.println("  affecté le " + todo.dateAssignation() 
			  + " a " + todo.assigneA());
            pw.println("  description : ");
            for (String desc : todo.description()) {
              pw.println("    " + desc);
            }
          }
 
          pw.close();
        } catch (IOException ioe) {
          messager.printMessage(Kind.ERROR, ioe.getMessage());
        }
      else
        messager.printMessage(Kind.NOTE, "Rien à faire");
    } else
      messager.printMessage(Kind.NOTE, "Fin des traitements");
 
    return true;
  }
}


Résultat :
C:\Documents and Settings\jmd\workspace\TestAnnotations>javac -cp ".;./bin;C:/Pr
ogram Files/Java/jdk1.6.0/lib/tools.jar" -processor com.jmdoudoux.tests.annotati
ons.outils.TodoProcessor2 com/jmdoudoux/tests/*.java
Note: Création du fichier Todo
Note: Fin des traitements
 
C:\Documents and Settings\jmd\workspace\TestAnnotations>type Todo.txt
Liste des todos
 
 
élément:MaClasse
  affecté le 11-11-2007 a JMD
  description :
    Corriger le bug dans le calcul
 
element:MaClasse1
  affecté le 07-11-2007 a JMD
  description :
    Ajouter le traitement des erreurs

 

9.9. Les ressources relatives aux annotations

La JSR 175 A Metadata Facility for the JavaTM Programming Language

La JSR 269 Pluggable Annotation Processing API

La JSR 250 Common Annotations

La page des annotations dans la documentation du JDK

La page des annotations dans le tutorial Java

La page d'utilisation de l'outil APT dans la documentation du JDK

Le projet open source XDoclet qui propose la génération de code à partir d'attributs dans le code


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