Les Annotations de Java 5.0
Date de publication : 05/07/2005
Par
F. Martini (adiGuba) (mes autres tutoriels)
Les Annotations permettent de marquer différents éléments du langage Java
avec des attributs particuliers, dans le but d'automatiser certains traitements
et même d'ajouter des traitements avant la compilation grâce au nouvel outil
du JDK : apt...
Avant propos...
Introduction
1. Qu'est-ce qu'une Annotation ?
1.1. Les Annotations standards
@Deprecated
@Override
@SuppressWarnings
1.2. Les Méta-Annotations
@Documented
@Inherit
@Retention
@Target
2. Création d'une Annotation
2.1. Une simple annotation : @TODO
2.2. Utiliser des attributs
2.3. Les membres et les valeurs par défauts
3. Annotation Processing Tool (APT)
3.1. AnnotationProcessorFactory
3.2. AnnotationProcessor
3.3. DeclarationVisitor
3.4. Compilation et utilisation
3.5. Un autre exemple : @EmptyConstructor
3.6. Restriction à l'utilisation d'APT
4. Les annotations et l'introspection
4.1. Recherche dynamique des Annotations
4.2. Exemple d'utilisation
Conclusion
Avant propos...
Je tiens à remercier l'équipe de rédacteurs Java de developpez.com pour leurs conseils avisés...
En particulier
vbrabant
et le responsable de l'équipe :
vedaer.
Introduction
Tiger, la version 5.0 de Java, est sûrement la version
de Java qui aura apporté le plus de changements dans le langage. Parmi
ceux-ci, les Annotations (également appelées Métadonnées) sont peut-être
les plus intéressantes ...
En effet, elles permettent non seulement de mieux documenter le code source,
mais également d'utiliser ces informations pendant l'exécution et même
d'interagir avec le compilateur grâce au nouvel utilitaire : APT
(Annotation Processing Tool).
 |
Avant de commencer, je vous invite à consulter l'article de
Lionel Roux sur les principales nouveautés de Java 5.0,
si ce n'est pas déjà fait :
http://lroux.developpez.com/article/java/tiger/
En effet, l'utilisation des Annotations et les codes sources présents
dans ce tutoriel utilisent une syntaxe propre à Java 5.0 (notamment
en ce qui concerne les Générics et les boucles for étendues).
|
1. Qu'est-ce qu'une Annotation ?
Une annotation permet de "marquer" certains éléments du langage Java
afin de leur ajouter une propriété particulière. Ces annotations peuvent
ensuite être utilisées à la compilation ou à l'exécution pour automatiser
certaines tâches...
Une annotation se déclare comme une interface, mis à part le symbole
@ devant le mot-clef interface qui permet de spécifier
qu'il s'agit d'une interface :
| Une annotation simple |
public @interface MonAnnotation {
}
|
Une annotation peut être utilisée sur plusieurs type d'éléments
(package, class, interface, enum, annotation, constructeur, méthode,
paramètre, champs d'une classe ou variable locale). Plusieurs annotations
différentes peuvent être utilisées sur un même élément mais on ne peut
pas utiliser deux fois la même annotation. En général l'annotation est
placée devant la déclaration de l'élément qu'elle marque :
| Exemple d'utilisation d'une annotation sur une classe |
@MonAnnotation
public class MaClasse {
}
|
1.1. Les Annotations standards
L'API standard de Java 5.0 propose seulement trois annotations.
Elle permettent d'interagir avec le compilateur Java.
@Deprecated
L'annotation @Deprecated vient remplacer le tag javadoc
@deprecated afin de signaler au compilateur que
l'élément marqué est déprécié et ne devrait plus être utilisé.
Le compilateur affichera un warning si l'élément est utilisé
dans du code non-déprécié (ou une 'note', selon la
configuration du compilateur).
| Utilisation de @Deprecated |
public class Maclasse {
@return
@Deprecated
public int getYear () {
return year;
}
}
|
Ainsi, il est désormais possible de savoir par réflection
si une méthode est dépréciée ou pas...
 |
Un petit bémol toutefois : contrairement au tag javadoc
@deprecated, cette annotation ne permet pas
de spécifier la raison de cette dépréciation...
Je vous conseille donc de l'utiliser conjointement avec
le tag javadoc @deprecated :
|
| Utilisation de @Deprecated et @deprecated |
public class Maclasse {
@return
@deprecated
@Deprecated
public int getYear () {
return year;
}
}
|
@Override
L'annotation @Override ne peut être utilisée que sur
une méthode afin de préciser au compilateur que cette méthode
est redéfinie et doit donc 'cacher' une méthode héritée d'une
classe parent. Si ce n'est pas le cas (par exemple si une faute
de frappe s'est glissée dans le nom de la méthode), le compilateur
doit afficher un erreur (et donc faire échouer la compilation).
Par exemple, si on redéfinit la méthode toString() de Object :
| Exemple |
@Override
public String toString() {
return "Texte";
}
|
 |
Cette annotation est doublement utile car non seulement
elle permet d'éviter des erreurs bêtes, mais elle rend
également le code plus lisible en distinguant les méthodes
redéfinies.
|
@SuppressWarnings
L'annotation @SuppressWarnings indique au compilateur
de ne pas afficher certains warnings. Le principal intérêt de
cette annotation est de cacher des warnings sur des parties
de code plus anciennes sans pour autant les cacher sur toute
l'application.
Elle reste toutefois à utiliser avec parcimonie.
Cette Annotation attend un attribut contenant le nom des
warnings qu'il faut supprimer, par exemple :
| Suppression du warning 'deprecation' pour la classe OldClass |
@SuppressWarnings("deprecation")
public class OldClass {
}
|
| Suppression des warning 'deprecation' et 'unchecked' pour la méthode m() |
@SuppressWarnings({"deprecation","unckeked"})
public int m () {
}
|
Le nom de ces types de warning sont les mêmes que ceux de
l'outil javac avec -Xlint, même si les compilateurs
peuvent définir d'autres types de warning.
Si le compilateur ne connait pas le type de warning indiqué, il doit
l'ignorer ou afficher un message dans le pire des cas, mais en aucun
cas il ne doit bloquer la compilation.
 |
Attention ! Aussi étrange que cela puisse paraître, le
compilateur javac 5.0 ne gère pas cette annotation...
Le 'bug' a déjà été signalé dans la base de données de Sun
et cette fonctionnalité a été implémentée dans la version
b29 de Mustang (le prochain Java). Donc a moins
d'une nouvelle mise à jours du JDK 5.0, il faudra encore attendre
pour pouvoir l'utiliser.
Pour plus d'information vous pouvez consulter la fiche du bug :
add support for jsr175's java.lang.SuppressWarnings.
|
1.2. Les Méta-Annotations
Les Méta-Annotations sont simplement des Annotations destinées
à marquer d'autres Annotations, généralement pour indiquer au
compilateur comment il doit les traiter.
Elles vont donc nous servir pour la conception de nos Annotations.
Chacune de ces Méta-Annotations répondent à un problème bien précis.
@Documented
La méta-annotation @Documented indique à l'outil
javadoc (ou à tout autre outil compatible) que
l'Annotation doit être présente dans la documentation générée
pour tous les éléments marqués.
Prenons comme exemple les deux annotations suivantes :
public @interface SimpleAnnotation {
}
import java.lang.annotation.Documented;
@Documented
public @interface DocAnnotation {
}
|
La classe suivante comprend deux méthodes marquées chacune
avec une des deux précédentes annotations :
| Exemple.java |
public class Exemple {
@SimpleAnnotation
public void method1 () {
}
@DocAnnotation
public void method2 () {
}
}
|
Ainsi, lorsqu'on consulte la documentation javadoc
de cette classe, on ne visualise que l'annotation de la
seconde méthode :
public void method1()
Commentaire de la méthode 1.
______________________________________________
@DocAnnotation
public void method2()
Commentaire de la méthode 2.
|
 |
Les Annotations peuvent bien sûr être utilisées par javadoc
grâce à son API Doclet. Pour plus d'informations sur le sujet je vous
invite à consulter la
documentation de l'outil Javadoc 5.0 sur le site officiel de Sun.
|
@Inherit
La méta-annotation @Inherit indique que l'annotation
sera héritée par tous les descendants de l'élément sur lequel
elle a été posée. Par défaut, les annotations ne sont pas
héritées par les éléments fils.
Prenons comme exemple les deux annotations suivantes :
public @interface SimpleAnnotation {
}
import java.lang.annotation.Inherit;
@Inherit
public @interface InheritAnnotation {
}
|
Et une classe marquée avec ces deux annotations :
| ExempleInherited.java |
@SimpleAnnotation
@InheritAnnotation
public class ExempleInherited {
}
|
Toutes les classes qui étendront ExempleInherited
seront alors automatiquement marquées par l'annotation
@InheritAnnotation mais pas par @SimpleAnnotation...
 |
Attention ! L'API précise bien que cette méta-annotation
est sans effet si l'annotation est utilisée sur autre chose qu'une
classe. Ainsi les annotations ne peuvent pas être héritées
d'une interface par exemple.
|
@Retention
La méta-annotation @Retention indique la "durée de vie"
de l'annotation, c'est à dire de quelle manière elle doit être gérée
par le compilateur.
Cette méta-annotation peut prendre une de ces trois valeurs :
| Valeur |
Description |
| RetentionPolicy.SOURCE |
Les annotations ne sont pas enregistrées dans le fichier *.class.
Elles ne sont donc accessibles que par des outils utilisant les fichiers
sources (compilateur, javadoc, etc...).
|
| RetentionPolicy.CLASS |
Les annotations sont enregistrées dans le fichier *.class
à la compilation mais elle ne sont pas utilisées par la machine virtuelle
à l'exécution de l'application. Elles peuvent toutefois être utilisées
par certains outils qui lisent directement les *.class.
Il s'agit du comportement par défaut si la méta-annotation n'est pas
présente.
|
| RetentionPolicy.RUNTIME |
Les annotations sont enregistrées dans le fichier *.class
à la compilation et elle sont utilisées par la machine virtuelle
à l'exécution de l'application. Elles peuvent donc être lues grâce
à l'API de réflection (plus de détails dans le chapitre sur l'introspection)... |
Par exemple, dans le code suivant, la classe 'Main' possède
trois annotations avec une "rétention" différente :
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
@interface SourceAnnotation { }
@Retention(RetentionPolicy.CLASS)
@interface ClassAnnotation { }
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeAnnotation {}
@SourceAnnotation
@ClassAnnotation
@RuntimeAnnotation
public class Main {
public static void main(String[] args) {
System.out.println ("Liste des annotations de la classe 'Main' :");
System.out.println ();
for (Annotation a : Main.class.getAnnotations() ) {
System.out.println ("\t * Annotation : " + a.annotationType().getSimpleName());
}
}
}
|
Lors de l'exécution de cette classe, on obtient donc l'affichage suivant :
Liste des annotations de la classe 'Main' :
* Annotation : RuntimeAnnotation
|
 |
RententionPolicy.CLASS permet au compilateur d'accéder à
l'annotation d'une classe dont il ne connaît pas la source, par
exemple lorsqu l'on hérite d'une classe d'une librairie.
|
@Target
La méta-annotation @Target permet de limiter le type
d'éléments sur lesquels l'annotation peut être utilisée.
Le tableau ci-dessous défini les différentes valeurs de @Target
correspondant aux différents éléments du langage.
Si la méta-annotation @Target est absente de la déclaration
d'une annotation, elle peut alors être utilisée sur tous ces éléments :
| Valeur |
Description |
| ElementType.ANNOTATION_TYPE |
L'annotation peut être utilisée sur d'autres annotations. |
| ElementType.CONSTRUCTOR |
L'annotation peut être utilisée sur des constructeurs. |
| ElementType.FIELD |
L'annotation peut être utilisée sur des champs d'une classe. |
| ElementType.LOCAL_VARIABLE |
L'annotation peut être utilisée sur des variables locales. |
| ElementType.METHOD |
L'annotation peut être utilisée sur des méthodes. |
| ElementType.PACKAGE |
L'annotation peut être utilisée sur des packages |
| ElementType.PARAMETER |
L'annotation peut être utilisée sur des paramètres d'une méthode ou d'un constructeur. |
| ElementType.TYPE |
L'annotation peut être utilisée sur la déclaration d'un type : class, interface (annotation comprise) ou d'une énumération (mot-clef enum). |
Par exemple, pour indiquer qu'une annotation ne peut être utilisée
que sur un constructeur :
@Target(ElementType.CONSTRUCTOR)
public @interface ConstructorAnnotation {
}
|
Et pour permette l'utilisation de l'annotation à la fois sur
les constructeurs et sur les méthodes :
@Target( {ElementType.CONSTRUCTOR, ElementType.METHOD} )
public @interface ConstructorAnnotation {
}
|
 |
Si on utilise une annotation sur un élément non autorisé,
la compilation générera une erreur...
|
2. Création d'une Annotation
Afin de détailler pas à pas la création d'un annotation, nous allons
prendre pour exemple une annotation @TODO qui permettra aux
développeurs de marquer le code source des tâches restant à accomplir.
2.1. Une simple annotation : @TODO
Nous allons donc commencer par créer notre annotation. Elle se
résume pour commencer à la simple déclaration suivante :
| TODO.java |
public @interface TODO {
}
|
La seconde étape est d'utiliser les méta-annotations standard
du chapitre précédent afin de modifier le comportement de notre annotation.
Pour cela il faut 'répondre' aux questions suivantes :
| Méta-Annotation |
Question |
Réponse pour @TODO |
| @Documented |
L'annotation doit-elle être documentée par javadoc ? |
Oui, afin d'avoir un aperçu des tâches restantes dans la documentation. |
| @Inherit |
L'annotation doit-elle être héritée (seulement si l'annotation est posée sur une classe) ? |
Non, on ne souhaite pas impacter les classes filles. |
| @Retention |
Quel est la 'durée de vie' de l'annotation ? |
SOURCE : Il n'est pas nécessaires de conserver cette annotation dans les fichiers *.class, on n'utilisera donc pas cette méta-annotation. |
| @Target |
L'annotation doit-elle se restreindre à certains éléments ? |
Non, on souhaite pouvoir annoter tous types d'éléments. |
Ceci se traduira par le code suivant :
| TODO.java |
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@Documented
@Retention(SOURCE)
public @interface TODO {
}
|
Notre annotation peut désormais être utilisée de la manière suivante :
| Exemple d'utilisation |
@TODO
public void doSometing () {
}
|
2.2. Utiliser des attributs
Pour l'instant notre annotation ne permet que de positionner
des marqueurs dans le code source, mais elle n'apporte aucune
information supplémentaire : c'est une annotation "marqueur".
Nous allons maintenant lui ajouter un attribut permettant
de décrire plus précisément la tâche restant à effectuer :
| TODO.java |
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@Documented
@Retention(SOURCE)
public @interface TODO {
String value();
}
|
La méthode value() représente l'attribut de notre annotation.
Il est inutile de préciser la portée de value() : elle
est implicitement public et ne peut pas prendre d'autre valeur.
Notre annotation s'utilise désormais de la manière suivante :
| Exemple d'utilisation |
@TODO(value="La gestion des exceptions est incorrecte...")
public void doSometing () {
}
|
Le type de retour de la méthode value() correspond au type
attendu par le membre value= lors de l'utilisation de l'annotation.
 |
Le nom value() n'est pas anodin. En effet, pour une annotation
à membre unique dont le nom est value(), il est possible
de ne pas spécifier le nom de l'attribut lors de l'utilisation de
l'annotation. Ce qui nous donne :
|
| Exemple d'utilisation |
@TODO("La gestion des exceptions est incorrecte...")
public void doSometing () {
}
|
Notre annotation devient un peu plus intéressante : lorsqu'on génère
la javadoc de la classe contenant la méthode doSometing(),
on obtient quelque chose du genre :
@TODO(value="La gestion des exceptions est incorrecte...")
public void doSometing()
Commentaire de la méthode...
|
Attention ! Les attributs d'une annotation n'acceptent que les éléments suivants :
- Un type primitif (boolean, int, float, etc.).
- Une chaîne de caractères (java.lang.String).
- Une référence de classe (java.lang.Class).
- Une Annotation (java.lang.annotation.Annotation).
- Un type énuméré (enum).
- Un tableau à une dimension d'un des types ci-dessus.
 |
Toutes les annotations héritent implicitement de l'interface java.lang.annotation.Annotation.
Si le type d'un membre correspond à un tableau, il faut utiliser une syntaxe proche
de l'ellipse pour le déclarer. Par exemple, pour un membre tab() correspondant à
un tableau de int, on peut utiliser les différentes formes suivantes :
|
| Utilisation d'un membre avec un tableau de 5 éléments |
@MonAnnotation (tab={1,2,3,4,5})
public clas MaClasse {
}
|
Si le tableau ne possède qu'un seul et unique élément, les accolades sont
alors inutiles :
| Utilisation d'un membre avec un tableau contenant un seul élément |
@MonAnnotation (tab=1)
public clas MaClasse {
}
|
2.3. Les membres et les valeurs par défauts
On va désormais enrichir notre annotation par un second membre
permettant d'indiquer un niveau de priorité à la tâche @TODO.
Ces niveaux de priorité seront définis via un type énuméré :
| TODO.java |
@Documented
@Retention(SOURCE)
public @interface TODO {
String value();
Level level();
public static enum Level { MINEUR, NORMAL, IMPORTANT };
}
|
Désormais, notre annotation sera obligatoirement utilisée de la
manière suivante :
| Exemple d'utilisation |
@TODO(value="La gestion des exceptions est incorrecte...", level=TODO.Level.NORMAL)
public void doSometing () {
}
|
 |
L'ordre des différents attributs de l'annotation n'est pas important, mais
il est obligatoire d'utiliser le nom de l'attribut.
Note : l'utilisation des import static de Java 5.0 devient ici
intéressante car il permet de simplifier la syntaxe :
|
| Exemple d'utilisation avec 'import static' : |
import static com.developpez.adiguba.annotation.TODO.Level.*;
@TODO(value="La gestion des exceptions est incorrecte...", level=NORMAL)
public void doSometing () {
}
|
Afin de permettre une utilisation plus souple, nous allons associer
une valeur par défaut à l'attribut level. Cela est possible
grâce au mot-clef default placé derrière la déclaration :
| TODO.java |
@Documented
@Retention(SOURCE)
public @interface TODO {
String value();
Level level() default Level.NORMAL;
public static enum Level { MINEUR, NORMAL, IMPORTANT };
}
|
Le membre level est désormais optionnel et sera implicitement
positionné à NORMAL s'il n'est pas précisé. Ainsi on peut désormais
utiliser notre annotation de différentes manières :
| Exemple d'utilisation |
@TODO("La gestion des exceptions est incorrecte...")
public void doSometing () {
}
@TODO(value="La gestion des exceptions est incorrecte...")
public void doSometing () {
}
@TODO(value="La gestion des exceptions est incorrecte...", level=NORMAL)
public void doSometing () {
}
@TODO(level=NORMAL, value="La gestion des exceptions est incorrecte...")
public void doSometing () {
}
|
 |
Il n'est pas possible de marquer plusieurs fois le même élément
avec la même annotation. Pour pallier à ce problème, il faut ruser en
utilisant une autre annotation conteneur. Par exemple, afin de
pouvoir utiliser notre annotation @TODO plusieurs fois
sur un même élément, on utilisera une annotation @TODOs qui
comporte un tableau d'annotation :
|
| TODOs.java |
@Documented
@Retention(SOURCE)
public @interface TODOs {
TODO[] value();
}
|
Qui sera donc utilisée de la manière suivante :
| Exemple d'utilisation : |
@TODOs({
@TODO("La gestion des exceptions est incorrecte..."),
@TODO(value="NullPointerException possible dans certain cas.",level=IMPORTANT)
})
public void doSometing () {
}
|
Notre annotation est désormais terminée. On pourrait éventuellement
y ajouter des membres supplémentaires, comme le nom du développeur
qui devra effectuer la tâche, ou la date à laquelle elle devra être
réalisée ... Mais on se contentera de ce simple exemple dans ce tutoriel.
Toutefois, certains EDIs proposent une fonctionnalité similaire. Par exemple
Eclipse propose des TaskTag qui permet de reporter certain mot-clefs
présents dans les commentaires dans un onglet de son interface...
Il est également possible d'utiliser l'API Doclets de javadoc afin de
traiter le tag @TODO à l'intérieur des commentaires lors de la génération
de la documentation...
Mais notre annotation possède deux avantages :
- Elle est indépendante de l'outil de développement.
- Elle permet de modifier le cours de la compilation grâce au nouvel outil du JDK 5.0 : APT !
3. Annotation Processing Tool (APT)
Le JDK 5.0 propose un nouvel outil : APT (Annotation Processing Tool).
Il s'agit d'un outil permettant d'effectuer des traitements sur les annotations
avant la compilation effective des sources Java. Il propose ainsi une vision
de la structure du code Java avant la compilation.
Il permet ainsi :
- De générer des messages (note, warning et error).
- De générer des fichiers (texte, binaire, source et classe Java).
Ceci avant de réellement compiler les fichiers *.java.
Pour cela, APT utilise les mêmes options de la ligne de commande
que javac, en plus des suivantes :
- -s dir : Spécifie le répertoire de base où seront placés les fichiers générés (par défaut dans le même répertoire que javac).
- -nocompile : Ne pas effectuer la compilation après le traitement des annotations.
- -print : Affiche simplement une représentation des éléments annotés (sans aucun traitement ni compilation).
- -A[key[=val]] : Permet de passer des paramètres supplémentaires destinés aux "fabriques".
- -factorypath path : Indique le(s) chemin(s) de recherche des "fabriques". Si absent, c'est le classpath qui sera utilisé.
- -factory classname : Indique le nom de la classe Java qui servira de "fabrique".
On peut voir que l'outil APT utilise des "fabriques" (factory en anglais)
qui permettent de créer les différents processeurs d'annotations dont on a besoin. En effet,
pour le traitement des codes sources, nous avons besoins de trois classes principales :
- AnnotationProcessorFactory : La fabrique qui sera utilisée par APT.
- AnnotationProcessor créé par la fabrique et utilisé par APT pour traiter les fichiers sources.
- Les Visitors qui permettent de visiter simplement les différentes déclarations/types d'un fichier source.
Ces différents objets sont définis dans l'API mirror de Sun qui fait partie
de la librairies tools.jar du JDK. Elle doit donc faire partie du
classpath pour pouvoir l'utiliser.
Comment ça marche ?
C'est à la fois simple et complexe : APT s'utilise comme javac pour
compiler des fichiers Java, mis à part le fait qu'il effectue un traitement
avant de lancer la compilation.
Il a besoin pour cela d'une "fabrique" qui lui donnera une instance
d'AnnotationProcessor qui effectuera une 'analyse' des sources Java
(plus exactement de la vue qu'APT propose de ces sources).
L'outil APT utilisera la "fabrique" spécifiée par le
paramètre -factory. Toutefois si ce paramètre est absent,
APT recherchera dans le classpath (ou le factorypath
si il est précisé) des fichiers nommées com.sun.mirror.apt.AnnotationProcessorFactory
dans le répertoire META-INF/services des différents répertoires
et archives jar...
Il s'agit d'un simple fichier texte contenant le nom complet des
différentes "fabriques" qui seront utilisées. Il est ainsi possible
d'utiliser plusieurs "fabriques" de plusieurs librairies simplement...
Nous allons donc maintenant créer notre propre "fabrique" pour
notre annotation @TODO afin d'afficher des messages pendant la
compilation avec APT...
3.1. AnnotationProcessorFactory
L'AnnotationProcessorFactory est une interface à implémenter
pour créer une "fabrique" utilisable par APT. Elle
nécessite l'implémentation de trois méthodes :
- public Collection<String> supportedAnnotationTypes();
Fournit la liste des annotations supportées en retournant une Collection contenant
le nom complet des annotations. Il est également possible d'utiliser le
caractères "*" tout comme dans les import.
- public Collection<String> supportedOptions()
Fournit la liste des options que la fabrique accepte et qui peuvent
être passées par la ligne de commande avec -Akey[=value] (le
préfixe "-A" doit être présent dans les chaînes retournées par cette
méthode). Pour l'instant, cette méthode n'est pas réellement utilisée par
APT, mais dans le futur il pourrait retourner une erreur si
une option est passé mais qu'aucune fabrique ne l'utilise...
- public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env)
Cette méthode sera appelée par APT afin d'obtenir un objet
AnnotationProcessor qui s'occupera du traitement sur les
annotations. Le paramètre adts comporte la liste des annotations
trouvées et env permet d'interagir avec l'environnement d'APT.
Notre fabrique pour l'annotation @TODO pourrait ressembler à cela :
| SimpleAnnotationProcessorFactory.java |
public class SimpleAnnotationProcessorFactory implements AnnotationProcessorFactory {
protected Collection<String> supportedAnnotationTypes =
Arrays.asList( TODO.class.getName(), TODOs.class.getName() );
protected Collection<String> supportedOptions =
Collections.emptyList();
public Collection<String> supportedAnnotationTypes() {
return supportedAnnotationTypes;
}
public Collection<String> supportedOptions() {
return supportedOptions;
}
public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds,
AnnotationProcessorEnvironment env) {
if (atds.isEmpty())
return AnnotationProcessors.NO_OP;
return new SimpleAnnotationProcessor(env);
}
}
|
La classe SimpleAnnotationProcessor représente notre processeur.
Nous allons donc voir ce qu'elle doit contenir.
3.2. AnnotationProcessor
AnnotationProcessor est une simple interface qui ne comporte
qu'une seule méthode :
public void process();
Cette méthode sera appelée une fois par APT pour le traitement
des annotations. Cette méthode ne possède aucun paramètre, il faut donc
que l'environnement d'APT lui soit passé par la fabrique :
| SimpleAnnotationProcessor.java |
public class SimpleAnnotationProcessor implements AnnotationProcessor {
protected final AnnotationProcessorEnvironment env;
@param
public SimpleAnnotationProcessor (AnnotationProcessorEnvironment env) {
this.env = env;
}
public void process() {
TODOVisitor todoVisitor = new TODOVisitor(env);
for ( Declaration d : env.getTypeDeclarations()) {
d.accept( DeclarationVisitors.getSourceOrderDeclarationScanner(
todoVisitor, DeclarationVisitors.NO_OP) );
}
}
}
|
La méthode process() parcours la liste des déclarations
de l'environnement d'APT, et utilise la méthode accept()
de ces déclarations pour les visiter. On utilise pour cela la méthode
DeclarationVisitors.getSourceOrderDeclarationScanner() qui permet
de scanner totalement une déclaration de type grâce au visitor.
3.3. DeclarationVisitor
L'interface DeclarationVisitor définit 16 méthodes visit***() permettant
chacune de visiter une déclaration particulière. Lorsqu'on passe une
instance de DeclarationVisitor à la méthode accept()
d'une Declaration, la méthode visit***() la plus appropriée
est appelée.
Dans notre AnnotationProcessor, nous utilisons la méthode statique
DeclarationVisitors.getSourceOrderDeclarationScanner() qui permet
de totalement scanner une déclaration et toutes ses sous-déclarations en
utilisant une instance spécifique de DeclarationVisitor.
Notre instance de DeclarationVisitor utilisera seulement la méthode
visitDeclaration() qui sera appelée pour n'importe quel
type de déclaration. Pour simplifier l'implémentation de l'interface
DeclarationVisitor, la classe SimpleDeclarationVisitor
implémente toutes ces méthodes. Du coup il suffit de redéfinir les
méthodes dont on a réellement besoin.
Dans un premier temps, notre visiteur se contentera d'afficher une note
dans la console contenant le libellé de l'annotation @TODO :
| TODOVisitor.java |
public class TODOVisitor extends SimpleDeclarationVisitor{
protected final AnnotationProcessorEnvironment env;
public TODOVisitor (AnnotationProcessorEnvironment env) {
this.env = env;
}
@TODO
@TODO
@Override
public void visitDeclaration(Declaration decl) {
TODO todo = decl.getAnnotation(TODO.class);
if (todo!=null)
printMessage(decl, todo);
|