Spécifiées dans la JSR175 et apparues avec Java 5.0, les annotations sont désormais
omniprésentes dans la plate-forme JavaEE et ses nombreux frameworks associés.
L'occasion de revenir sur les techniques permettant de créer ses propres
annotations.
Introduction
Les annotations que nous introduisons dans notre code sont à destination
d'un "environnement d'exécution" qui va utiliser ces annotations pour effectuer
des traitements supplémentaires ou configurer les traitements qu'il a
l'habitude de faire. Ainsi si on prend le compilateur java comme "environnement
d'exécution", l'annotation @Override lui indiquera que la
méthode annotée a été redéfinie, le compilateur se chargera de vérifier qu'il
s'agit bien d'une redéfinition. Notons que cette information ne sera nécessaire
qu'en phase de compilation. Pour l'annotation @Deprecated qui indique si une
méthode ou une classe est dépréciée, ainsi placée avant une méthode ou avant un
classe, cette annotation n'aura pas le même sens.
Définition d'une annotation
Nous venons de le voir une annotation peut avoir une certaine portée,
l'annotation @Deprecated n'était nécessaire que pour la phase
de compilation. Par ailleurs certaines annotations s'appliquent à des méthodes,
d'autres à des classes, des classes d'un certains type, etc... voilà les deux
aspects que nous aurons à configurer sur nos annotations, et cette
configuration se fera via... des annotations (ou des meta-annotations si vous
préférez)
Définition de la portée ou "Rétention"
Vos classes Java, dans lesquelles vous placez vos annotations, passent par 3
états : code source, bytecode et bytecode interprété et chargé par le
classloader de votre JVM. La méta-annotation @Retention vous permet d'indiquer
dans quel états vous souhaitez utiliser votre annotation. Pour vous faciliter
la tâches ces 3 valeurs sont stockées dans l'enum RetentionPolicy (SOURCE,
CLASS, RUNTIME). Si on prend l'exemple de l'annotation @override, elle aura
comme sera définie comme suit :
@Retention(RetentionPolicy.SOURCE)
A contrario des spécifications 3.0 des EJB ou des Servlets, les nombreuses
annotations utilisées, le seront en phase d'exécution car le serveur
d'application dans lequel vous déploierez vos composants aura besoin de
découvrir les configuration associée à chacun de ces composants (url-pattern,
name, init-param, session, stateless, stataful, entity, etc...).
Prenons l'exemple d'une annotation @Auteur qui nous
permettrez d'indiquer dans le code quel est l'auteur de ce dernier, voici
comment nous définirions une telle annotation. Dans le fichier
Auteur.java :
package com.jcc.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Auteur {
}
Notez que l'annotation est placée dans un package comme n'importe quel
élément (classe, enum, interface) Java et que le mot clé @interface permet
d'indiquer qu'il s'agit de la définition d'une annotation
Définition de la cible ou "Target"
La meta-annotation @Target permet de définir une contrainte
sur le contexte d'utilisation de notre annotation : classe, méthode,
constructeur, attribut, etc. Là encore toutes les valeurs possibles sont
stockées dans l'enum ElementType :
- TYPE – s'applique uniquement au type java spécifié (classe, interface,
enum, annotation)
- FIELD – pour les attributs static ou d'instance d'une classe
- METHOD – s'applique aux méthodes
- PARAMETER – pour les paramètres d'une méthode
- CONSTRUCTOR – destiné aux constructeurs
- LOCAL_VARIABLE – pour les variables locales d'une méthode
- ANNOTATION_TYPE – pour les annotations dont le type est à précises
- PACKAGE – s'applique à l'intégralité du package indiqué
Imaginons que nous souhaitions appliquer notre @Auteur aux méthodes et aux
constructeurs, voici comment nous devrions définir notre annotation :
package com.jcc.annotations;
import java.lang.annotation.*;
@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auteur {
}
passage de propriétés
Dernier point, il est généralement nécessaire de passer des paramètres à nos
annotations. Dans notre cas, il s'agirait de passer le nom de l'auteur qui
souhaite signer la méthode ou le constructeur qu'il vient d'écrire. Pour
pouvoir passer de tels paramètres il faut les déclarer lors de la définition de
l'annotation comme suit :
package com.jcc.annotations;
import java.lang.annotation.*;
@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auteur {
String nom();
}
Si vous souhaiter rendre ce paramètre optionnel, donnez lui une valeur par
défaut comme indiqué dans l'exemple ci-dessous :
package com.jcc.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auteur {
String nom() default "Auteur par défaut";
}
Utilisation de l'annotation
Une fois cette annotation compilée vous pourrez l'utilisez directement avant
vos déclaration de méthode ou de constructeur comme dans l'exemple :
public class ClasseDeTest {
@Auteur
public ClasseDeTest() {
}
@Auteur(nom="John Smith")
public String toString() {
return super.toString();
}
}
Les informations sur l'auteur de cette méthode et de ce constructeur seront
disponibles pour le compilateur, le classloader et la JVM, ainsi à l'exécution
et via l'api d'introspection de Java il sera possible de retrouver l'auteur de
cette méthode ou de ce constructeur.