Exploiter les annotations Java au runtime et les méta-annotations

  1. Lire une annotation depuis un élément du programme

En Java, toute entité susceptible de porter une annotation — classe, constructeur, champ, méthode, package… — implémente l'interface java.lang.reflect.AnnotatedElement. Cette interface offre notamment les méthodes suivantes :

<T extends Annotation> T getAnnotation(Class<T> type)
Annotation[] getDeclaredAnnotations()
boolean isAnnotationPresent(Class<? extends Annotation> type)

Pour pouvoir interroger ces méthodes pendant l'exécution, l'annotation concernée doit être annotée avec @Retention(RetentionPolicy.RUNTIME).

  1. Exemple d'introspection

Définissons une annotation personnalisée conservée au runtime :

package dev.demo.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
    String nom();
    int priorite() default 0;
}

Appliquons-la sur une méthode, puis lisons-la par réflexion :

package dev.demo.annotations;

import java.lang.reflect.Method;

public class Service {
    @Action(nom = "demarrage", priorite = 1)
    public void lancer() {
        System.out.println("démarrage");
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method m = Service.class.getMethod("lancer");
        if (m.isAnnotationPresent(Action.class)) {
            Action a = m.getAnnotation(Action.class);
            System.out.println(a.nom() + " (priorité " + a.priorite() + ")");
            System.out.println("type de l'annotation : " + a.annotationType());
        }
    }
}
  1. Restreindre les cibles avec @Target

L'annotation @Target, définie dans java.lang.annotation, indique les éléments du langage susceptibles de recevoir l'annotation. Ses valeurs sont les constantes de l'énumération ElementType :

TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR,
LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, MODULE, TYPE_PARAMETER, TYPE_USE

Exemple d'annotation limitée aux classes et aux méthodes :

package dev.demo.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Tracable {
    String valeur() default "";
}
  1. Inclure l'annotation dans la documentation avec @Documented

Si une annotation porte @Documented, les outils de génération de documentation, tels que Javadoc, la mentionneront dans la page de l'élément annoté.

package dev.demo.annotations;

import java.lang.annotation.Documented;

@Documented
public @interface Notice {
    String message();
}
  1. Transmettre une annotation aux sous-classes avec @Inherited

Lorsqu'une annnotation est annotée avec @Inherited et qu'elle s'aplique à une classe, les classes filles héritent de cette annotation. Cela ne s'applique pas aux interfaces ni aux méthodes redéfinies.

package dev.demo.annotations;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
    String nom();
}
@Role(nom = "admin")
public class Compte { }

public class CompteAdmin extends Compte { }

// CompteAdmin.class.getAnnotation(Role.class) renvoie l'annotation héritée
  1. Rappel sur les méta-annotations et les valeurs par défaut

Java fournit quatre méta-annotations standard dans le package java.lang.annotation :

  • @Target : cibles autorisées.
  • @Retention : durée de vie (SOURCE, CLASS ou RUNTIME).
  • @Documented : inclusion dans la documentation publique.
  • @Inherited : transmission aux classes filles.

Une annotation personnalisée se déclare avec @interface. Chaque méthode de l'annotation représente un paramètre ; son type de retour est le type du paramètre. Seuls les types suivants sont autorisés : types primitifs, String, Class, enum, une autre annotation, ou un tableau de ces types. Une valeur par défaut s'indique via default.

public @interface Config {
    String hote() default "localhost";
    int port() default 8080;
}

Les éléments non primitifs ne peuvent valoir null ; on utilise souvent une chaîne vide ou -1 comme conevntion pour indiquer l'absence de valeur explicite.

Étiquettes: Java Annotations Java Introspection Java Meta-Annotations Javadoc

Publié le 23 juin à 01h33