Maîtriser les Annotations Java : Créer des annotations personnalisées

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 constructeurs
  • FIELD : pour les champs
  • LOCAL_VARIABLE : pour les variables locales
  • METHOD : pour les méthodes
  • PACKAGE : pour les paquets
  • PARAMETER : pour les paramètres
  • TYPE : 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 source
  • CLASS : conservée dans le fichier .class mais ignorée à l'exécution
  • RUNTIME : 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é.

Étiquettes: Java Annotation Méta-annotation rétroaction réflexion

Publié le 2 juillet à 23h24