Pour maîtriser les annotations en Java, il est essentiel de savoir en définir et les utiliser. Avant de créer nos propres annotations, nous devons comprendre les méta-annotations que Java fournit ainsi que la syntaxe pour les définir.
Les Méta-annotations
Les méta-annotations servent à annoter d'autres annotations. Java 5 a introduit quatre méta-annotations standard qui fournissent des métadonnées sur les types d'annotation. Elles se trouvent dans le paquet java.lang.annotation.
@Target
Cette méta-annotation spécifie à quels éléments Java une annotation peut être appliquée. Elle définit donc le champ d'application d'une annotation.
Paramètres possibles (ElementType) :
CONSTRUCTOR: pour les constructeursFIELD: pour les champsLOCAL_VARIABLE: pour les variables localesMETHOD: pour les méthodesPACKAGE: pour les paquetsPARAMETER: pour les paramètresTYPE: pour les classes, interfaces (y compris les types d'annotation) ou les déclarations d'enum
Exemple :
@Target(ElementType.TYPE)
public @interface EntityTable {
String tableName() default "";
}
@Target(ElementType.FIELD)
public @interface TransientField {
// Annotation marqueur sans élément
}
L'annotation EntityTable peut être placée sur une classe, une interface ou un enum. L'annotation TransientField ne peut être appliquée qu'aux champs.
@Retention
Cette méta-annotation indique jusqu'à quelle étape du cycle de compilation une annotation doit être conservée.
Paramètres possibles (RetentionPolicy) :
SOURCE: conservée uniquement dans le code sourceCLASS: conservée dans le fichier .class mais ignorée à l'exécutionRUNTIME: conservée et accessible via la réflexion à l'exécution
Exemple :
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnMapping {
String columnName() default "";
String getter() default "";
String setter() default "";
}
Grâce à RetentionPolicy.RUNTIME, un processeur d'annotation peut accéder aux informations de ColumnMapping via la réflexion pendant l'exécution.
@Documented
Une annotation marquée par @Documented apparaîtra dans la documentation Javadoc générée. C'est une annotation marquante (sans élément).
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BusinessEntity {
String module() default "";
}
@Inherited
Cette méta-annotation indique que l'annotation peut être héritée par les classes enfants. Lorsqu'une classe est annotée avec une annotation décorée de @Inherited, ses sous-classes hériteront également de cette annotation.
Limites : L'héritage ne s'applique pas aux interfaces ni aux méthodes redéfinies.
Exemple :
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Auditable {
String auditLevel() default "INFO";
String auditor() default "system";
}
Créer une annotation personnalisée
Les annotations personnalisées sont définies avec l'interface @interface. Chaque méthode déclare un élément (paramètre) de l'annotation. Les types de retour autorisés sont : les types primitifs, String, Class, enum, d'autres annotations et des tableaux de ces types.
Conventions :
- Si l'annotation a un seul élément, il est d'usage de le nommer
value(). - Les valeurs par défaut sont fréquentes pour éviter la valeur
null(souvent une chaîne vide ou une valeur numérique spécifique comme-1).
Exemple complet : annotations pour une bibliothèque de persistance
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Repository {
String dataSource() default "default";
boolean cacheable() default false;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersistableField {
String columnName();
int maxLength() default 255;
boolean nullable() default true;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface QueryMethod {
String statement();
boolean readOnly() default true;
}
Classe d'utilisation
@Repository(dataSource = "mainDB", cacheable = true)
public class CustomerDAO {
@PersistableField(columnName = "cust_id", maxLength = 36)
private String id;
@PersistableField(columnName = "full_name", maxLength = 100)
private String name;
@QueryMethod(statement = "SELECT * FROM customers WHERE id = ?")
public Customer findById(String id) {
// Logique d'implémentation
return null;
}
}
Gestion des valeurs par défaut
Pour indiquer l'absence d'une valeur, on utilise des valeurs conventionnelles comme -1 pour un entier ou "" pour une chaîne.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dependency {
String name() default "";
String version() default "";
String repositoryUrl() default "";
}
La puissance des annotations réside dans leur traitement. Le prochain pas est d'apprendre à lire et traiter les annotations en utilisant la réflexion pour leur donner une véritable fonctionnalité.