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.