W3docs

Introduction aux annotations Java

Ce que sont les annotations Java, comment elles attachent des métadonnées au code et où elles sont traitées.

Une annotation est un marqueur que l'on attache à un élément Java — une classe, une méthode, un champ, un paramètre, une utilisation de type — afin d'y ajouter des métadonnées. L'annotation elle-même ne fait rien. C'est une étiquette qu'un autre morceau de code (le compilateur, un framework, un IDE, un outil de build) lit ultérieurement et exploite. @Override sur une méthode indique au compilateur « je remplace une méthode de la superclasse ; signale-moi si ce n'est pas le cas. » @Test sur une méthode indique à JUnit « c'est un test. » @Entity sur une classe indique à JPA « mappe-la vers une table de base de données. » Dans chaque cas, l'annotation ne fait que transporter de l'information ; le comportement réside dans le processeur qui la lit.

Vous avez déjà rencontré des annotations tout au long de ce livre — @Override sur les méthodes redéfinies, @FunctionalInterface sur les interfaces à méthode unique, @SuppressWarnings pour faire taire le compilateur. Cette partie du livre porte sur le système sous-jacent : ce qu'est une annotation, quand elle est disponible (source uniquement, fichier classe ou runtime), comment en écrire une, et comment les processeurs les consomment.

La forme d'une annotation

Syntaxiquement, une annotation est le symbole @ suivi du nom de l'annotation, appliqué immédiatement avant l'élément annoté :

@Override
public String toString() { ... }

@Deprecated
public void oldApi() { ... }

@SuppressWarnings("unchecked")
List<String> list = (List<String>) raw;

Certaines annotations acceptent des éléments (leur équivalent de champs). Les valeurs des éléments se placent entre parenthèses :

@SuppressWarnings("unchecked")                       // one element, value "unchecked"
@SuppressWarnings({"unchecked", "rawtypes"})         // array of strings

@RequestMapping(path = "/users", method = GET)       // two named elements

Quand une annotation déclare un seul élément appelé value, son nom peut être omis — @SuppressWarnings("unchecked") est un raccourci pour @SuppressWarnings(value = "unchecked").

Ce que les annotations ne sont pas

Trois points négatifs pour éviter les malentendus les plus fréquents :

  • Les annotations n'exécutent pas de code. Écrire @Cached à côté d'une méthode ne met rien en cache. Il faut que quelque chose d'autre détecte @Cached et ajoute le comportement de mise en cache. L'annotation est un drapeau, pas une fonction.
  • Les annotations ne sont pas des commentaires. Les commentaires disparaissent à la compilation ; les annotations sont des constructions de premier ordre du langage. Elles participent au système de types, peuvent être conservées dans le fichier classe et lues à l'exécution via la réflexion.
  • Les annotations ne remplacent pas un code clair. Une longue pile d'annotations au-dessus d'une classe représente de la densité d'information, pas toujours de la clarté. Les frameworks qui s'appuient fortement sur les annotations (Spring, JPA, JAX-RS) paient la commodité d'une courbe d'apprentissage et d'un coût à l'exécution.

La durée de vie d'une annotation

Chaque annotation possède une politique de rétention qui détermine combien de temps les métadonnées survivent :

  • SOURCE — le compilateur la lit puis la supprime. @Override et @SuppressWarnings fonctionnent ainsi ; le bytecode ne conserve aucune trace d'elles.
  • CLASS — l'annotation est écrite dans le fichier .class mais n'est pas chargée par la JVM à l'exécution. C'est la valeur par défaut. Les outils qui inspectent le bytecode (analyseurs statiques, post-processeurs) peuvent la lire.
  • RUNTIME — l'annotation est conservée jusqu'au bout ; la réflexion peut interroger n'importe quelle classe, méthode ou champ pour obtenir ses annotations à l'exécution. Des frameworks comme Spring et Jackson s'appuient sur cela.

Vous verrez la méta-annotation @Retention(...) qui définit cette politique dans le chapitre Méta-annotations. En résumé : choisissez la rétention selon qui doit lire l'annotation — le compilateur, un outil au moment du build ou du code à l'exécution.

Où une annotation peut aller

Chaque annotation possède également une cible — l'ensemble des éléments de programme qu'elle peut légalement annoter. Les cibles courantes :

  • TYPE — classes, interfaces, enums.
  • METHOD — méthodes.
  • FIELD — champs.
  • PARAMETER — paramètres de méthode.
  • CONSTRUCTOR — constructeurs.
  • LOCAL_VARIABLE — déclarations de variables locales.
  • ANNOTATION_TYPE — autres déclarations d'annotation (méta-annotations).
  • PACKAGE — packages, via package-info.java.
  • TYPE_USEtoute utilisation d'un type, y compris les paramètres génériques et les casts (Java 8+).

Si vous placez une annotation là où son @Target ne le permet pas, le compilateur refuse. Essayer @Override sur une déclaration de classe est une erreur de compilation, car @Override cible METHOD.

Qui lit les annotations

Trois endroits consomment les données des annotations :

  1. Le compilateur. Les annotations intégrées comme @Override, @SafeVarargs et @FunctionalInterface sont vérifiées par javac lui-même.
  2. Les processeurs d'annotations. Des outils pluggables à la compilation qui s'exécutent pendant javac. Ils peuvent lire les annotations sur les sources compilées et générer de nouveaux fichiers sources en réponse. Lombok, Dagger, le métamodèle statique d'Hibernate et le framework Micronaut fonctionnent ainsi.
  3. La réflexion à l'exécution. Method.getAnnotations(), Class.getAnnotation(...), etc. renvoient les instances d'annotation pour tout élément avec une rétention RUNTIME. C'est ainsi que Spring décide quoi injecter, que JUnit trouve vos tests, et que Jackson mappe le JSON.

Les deux premiers n'ont besoin d'aucun support de la machine virtuelle au-delà de ce que fournit javac. Le troisième nécessite que l'annotation soit écrite dans le fichier classe et chargée par le runtime.

Exemple concret : inspecter les annotations de votre propre classe

L'objectif de cet exemple est de montrer que @Override, @Deprecated et @SuppressWarnings se ressemblent dans le code source mais se comportent différemment une fois la classe compilée. Le programme déclare une classe avec plusieurs annotations, puis demande à la réflexion ce qu'elle peut réellement voir.

java— editable, runs on the server

Ce que l'exécution nous apprend :

  • La boucle au niveau de la classe a vu @Deprecated sur Greeter mais rien d'autre — @Deprecated a une rétention RUNTIME. @Override et @SuppressWarnings sont SOURCE, donc le compilateur les a supprimées avant d'écrire le fichier classe et la réflexion ne peut pas les récupérer.
  • La boucle par méthode n'a affiché @Deprecated que sur oldHello. Même si toString était déclarée @Override et cast déclarée @SuppressWarnings("unchecked"), aucune de ces annotations n'est parvenue au fichier classe. L'information existait au moment où javac s'est exécuté — c'est ainsi que la vérification de redéfinition a eu lieu — puis a été supprimée.
  • La vérification de rétention l'a clairement montré : @Override et @SuppressWarnings portent @Retention(SOURCE) dans leurs propres déclarations, tandis que @Deprecated porte @Retention(RUNTIME). La rétention est une propriété du type d'annotation, pas de la façon dont elle est utilisée.
  • Lire @Deprecated sur Greeter via cls.getAnnotation(Deprecated.class) a renvoyé un proxy dont les méthodes d'éléments (since(), forRemoval()) retournaient les valeurs écrites dans le source. C'est l'interface d'exécution vers les métadonnées d'annotation : une instance dont les éléments sont des méthodes accesseurs.
  • Le message à retenir pour choisir la rétention : si le seul consommateur est javac (vérification de redéfinition, suppression d'avertissements), utilisez SOURCE. Si un framework doit lire l'annotation pendant l'exécution du programme (DI, ORM, liaison JSON), utilisez RUNTIME. Le chapitre sur les méta-annotations explique comment le déclarer pour vos propres types d'annotation.

Ce qui vient dans cette partie

Les chapitres suivants de cette partie vous guideront à travers :

  • Les annotations intégrées les plus courantes de java.lang@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface.
  • Les méta-annotations qui configurent les vôtres — @Retention, @Target, @Documented, @Inherited, @Repeatable.
  • L'écriture de types d'annotation personnalisés et leur lecture via la réflexion.
  • L'API de traitement d'annotations à la compilation que les frameworks utilisent pour générer du code.

L'arc va de « ce que le langage vous donne comme annotations » à « ce que vous pouvez construire par-dessus. »

Pratique

Pratique
Un coéquipier écrit `@Cached` à côté d'une méthode et s'attend à ce que le deuxième appel retourne un résultat mis en cache. Qu'a-t-il mal compris au sujet des annotations ?
Un coéquipier écrit `@Cached` à côté d'une méthode et s'attend à ce que le deuxième appel retourne un résultat mis en cache. Qu'a-t-il mal compris au sujet des annotations ?
Was this page helpful?