Comment décrire le code Java avec des annotations

Vous avez probablement rencontré des situations dans lesquelles vous devez associer des métadonnées (données qui décrivent d'autres données) avec des classes, des méthodes et / ou d'autres éléments d'application. Par exemple, votre équipe de programmation peut avoir besoin d'identifier les classes inachevées dans une grande application. Pour chaque classe inachevée, les métadonnées incluraient probablement le nom du développeur chargé de terminer la classe et la date d'achèvement prévue de la classe.

Avant Java 5, les commentaires étaient le seul mécanisme flexible que Java avait à offrir pour associer des métadonnées à des éléments d'application. Cependant, les commentaires sont un mauvais choix. Étant donné que le compilateur les ignore, les commentaires ne sont pas disponibles au moment de l'exécution. Et même s'ils étaient disponibles, le texte devrait être analysé pour obtenir des éléments de données cruciaux. Sans normaliser la façon dont les éléments de données sont spécifiés, ces éléments de données pourraient s'avérer impossibles à analyser.

télécharger Obtenir le code Téléchargez le code source pour des exemples dans ce didacticiel Java 101. Créé par Jeff Friesen pour.

Mécanismes d'annotation non standard

Java fournit des mécanismes non standard pour associer des métadonnées à des éléments d'application. Par exemple, le transientmot réservé vous permet d' annoter (associer des données à) les champs qui doivent être exclus lors de la sérialisation.

Java 5 a tout changé en introduisant des annotations , un mécanisme standard pour associer des métadonnées à divers éléments d'application. Ce mécanisme se compose de quatre éléments:

  • Un @interfacemécanisme pour déclarer les types d'annotations.
  • Les types de méta-annotations, que vous pouvez utiliser pour identifier les éléments d'application auxquels s'applique un type d'annotation; pour identifier la durée de vie d'une annotation (une instance d'un type d'annotation); et plus.
  • Prise en charge du traitement des annotations via une extension de l'API Java Reflection (à discuter dans un prochain article), que vous pouvez utiliser pour découvrir les annotations d'exécution d'un programme, et un outil généralisé pour le traitement des annotations.
  • Types d'annotations standard.

J'expliquerai comment utiliser ces composants tout au long de cet article.

Déclaration des types d'annotations avec @interface

Vous pouvez déclarer un type d'annotation en spécifiant le @symbole immédiatement suivi du interfacemot réservé et d'un identifiant. Par exemple, le listing 1 déclare un type d'annotation simple que vous pouvez utiliser pour annoter du code thread-safe.

Liste 1:ThreadSafe.java

public @interface ThreadSafe {}

Après avoir déclaré ce type d'annotation, préfixez les méthodes que vous considérez thread-safe avec des instances de ce type en ajoutant @immédiatement le nom du type aux en-têtes de méthode. Le listing 2 offre un exemple simple où la main()méthode est annotée @ThreadSafe.

Listing 2:AnnDemo.java (version 1)

classe publique AnnDemo {@ThreadSafe public static void main (String [] args) {}}

ThreadSafeles instances ne fournissent aucune métadonnée autre que le nom du type d'annotation. Cependant, vous pouvez fournir des métadonnées en ajoutant des éléments à ce type, où un élément est un en-tête de méthode placé dans le corps du type d'annotation.

En plus de ne pas avoir de corps de code, les éléments sont soumis aux restrictions suivantes:

  • L'en-tête de méthode ne peut pas déclarer de paramètres.
  • L'en-tête de méthode ne peut pas fournir de clause throws.
  • Le type de retour de la tête de procédé doit être un type primitif (par exemple int), java.lang.String, java.lang.Class, un enum, un type d'annotation, ou un réseau d'un de ces types. Aucun autre type ne peut être spécifié pour le type de retour.

Comme autre exemple, le listing 3 présente un ToDotype d'annotation avec trois éléments identifiant un travail de codage particulier, spécifiant la date à laquelle le travail doit être terminé et nommant le codeur responsable de l'achèvement du travail.

Listing 3:ToDo.java (version 1)

public @interface ToDo {int id (); String finishDate (); Codeur de chaîne () par défaut "n / a"; }

Notez que chaque élément ne déclare aucun paramètre ou clause throws, a un type de retour légal ( intou String) et se termine par un point-virgule. En outre, le dernier élément révèle qu'une valeur de retour par défaut peut être spécifiée; cette valeur est renvoyée lorsqu'une annotation n'affecte pas de valeur à l'élément.

Le listing 4 utilise ToDopour annoter une méthode de classe inachevée.

Listing 4:AnnDemo.java (version 2)

public class AnnDemo {public static void main (String [] args) {String [] cities = {"New York", "Melbourne", "Beijing", "Moscou", "Paris", "London"}; trier (villes); } @ToDo (id = 1000, finishDate = "10/10/2019", coder = "John Doe") static void sort (Object [] objets) {}}

Le listing 4 attribue un élément de métadonnées à chaque élément; par exemple, 1000est affecté à id. Contrairement coderaux éléments idet finishDatedoivent être spécifiés; sinon, le compilateur signalera une erreur. Lorsqu'aucune codervaleur n'est attribuée, il prend sa "n/a"valeur par défaut .

Java fournit un String value()élément spécial qui peut être utilisé pour renvoyer une liste d'éléments de métadonnées séparés par des virgules. Le listing 5 montre cet élément dans une version refactorisée de ToDo.

Listing 5:ToDo.java (version 2)

public @interface ToDo {Valeur de chaîne (); }

Quand value()est le seul élément d'un type d'annotation, vous n'avez pas à spécifier valueet l' =opérateur d'affectation lors de l'affectation d'une chaîne à cet élément. Le listing 6 montre les deux approches.

Listing 6:AnnDemo.java (version 3)

public class AnnDemo {public static void main (String [] args) {String [] cities = {"New York", "Melbourne", "Beijing", "Moscou", "Paris", "London"}; trier (villes); } @ToDo (value = "1000,10 / 10/2019, John Doe") static void sort (Object [] objects) {} @ToDo ("1000,10 / 10/2019, John Doe") static boolean search ( Object [] objets, clé d'objet) {return false; }}

Utilisation des types de méta-annotations - le problème de la flexibilité

Vous pouvez annoter des types (par exemple, des classes), des méthodes, des variables locales, etc. Cependant, cette flexibilité peut être problématique. Par exemple, vous voudrez peut-être limiter ToDouniquement les méthodes, mais rien ne l'empêche d'être utilisé pour annoter d'autres éléments d'application, comme illustré dans le Listing 7.

Listing 7:AnnDemo.java (version 4)

@ToDo ("1000,10 / 10/2019, John Doe") public class AnnDemo {public static void main (String [] args) {@ToDo (value = "1000,10 / 10/2019, John Doe") String [] villes = {"New York", "Melbourne", "Pékin", "Moscou", "Paris", "Londres"}; trier (villes); } @ToDo (value = "1000,10 / 10/2019, John Doe") static void sort (Object [] objects) {} @ToDo ("1000,10 / 10/2019, John Doe") static boolean search ( Object [] objets, clé d'objet) {return false; }}

Dans le Listing 7, ToDoest également utilisé pour annoter la AnnDemoclasse et citiesla variable locale. La présence de ces annotations erronées peut embrouiller quelqu'un qui examine votre code, ou même vos propres outils de traitement d'annotations. Pour les moments où vous avez besoin de restreindre la flexibilité d'un type d'annotation, Java propose le Targettype d'annotation dans son java.lang.annotationpackage.

Targetest un type de méta-annotation  - un type d'annotation dont les annotations annotent des types d'annotations, par opposition à un type non-méta-annotation dont les annotations annotent des éléments d'application, tels que des classes et des méthodes. Il identifie les types d'éléments d'application auxquels un type d'annotation est applicable. Ces éléments sont identifiés par Targetl' ElementValue[] value()élément s .

java.lang.annotation.ElementType is an enum whose constants describe application elements. For example, CONSTRUCTOR applies to constructors and PARAMETER applies to parameters. Listing 8 refactors Listing 5’s ToDo annotation type to restrict it to methods only.

Listing 8:ToDo.java (version 3)

import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface ToDo { String value(); }

Given the refactored ToDo annotation type, an attempt to compile Listing 7 now results in the following error message:

AnnDemo.java:1: error: annotation type not applicable to this kind of declaration @ToDo("1000,10/10/2019,John Doe") ^ AnnDemo.java:6: error: annotation type not applicable to this kind of declaration @ToDo(value="1000,10/10/2019,John Doe") ^ 2 errors

Additional meta-annotation types

Java 5 introduced three additional meta-annotation types, which are found in the java.lang.annotation package:

  • Retention indicates how long annotations with the annotated type are to be retained. This type’s associated java.lang.annotation.RetentionPolicy enum declares constants CLASS (compiler records annotations in class file; virtual machine doesn’t retain them to save memory — default policy), RUNTIME (compiler records annotations in class file; virtual machine retains them), and SOURCE (compiler discards annotations).
  • Documented indicates that instances of Documented-annotated annotations are to be documented by javadoc and similar tools.
  • Inherited indicates that an annotation type is automatically inherited.

Java 8 introduced the java.lang.annotation.Repeatable meta-annotation type. Repeatable is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. In other words, you can apply multiple annotations from the same repeatable annotation type to an application element, as demonstrated here:

@ToDo(value = "1000,10/10/2019,John Doe") @ToDo(value = "1001,10/10/2019,Kate Doe") static void sort(Object[] objects) { }

This example assumes that ToDo has been annotated with the Repeatable annotation type.

Processing annotations

Annotations are meant to be processed; otherwise, there’s no point in having them. Java 5 extended the Reflection API to help you create your own annotation processing tools. For example, Class declares an Annotation[] getAnnotations() method that returns an array of java.lang.Annotation instances describing annotations present on the element described by the Class object.

Listing 9 presents a simple application that loads a class file, interrogates its methods for ToDo annotations, and outputs the components of each found annotation.

Listing 9:AnnProcDemo.java

import java.lang.reflect.Method; public class AnnProcDemo { public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("usage: java AnnProcDemo classfile"); return; } Method[] methods = Class.forName(args[0]).getMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(ToDo.class)) { ToDo todo = methods[i].getAnnotation(ToDo.class); String[] components = todo.value().split(","); System.out.printf("ID = %s%n", components[0]); System.out.printf("Finish date = %s%n", components[1]); System.out.printf("Coder = %s%n%n", components[2]); } } } }

After verifying that exactly one command-line argument (identifying a class file) has been specified, main() loads the class file via Class.forName(), invokes getMethods() to return an array of java.lang.reflect.Method objects identifying all public methods in the class file, and processes these methods.

Method processing begins by invoking Method’s boolean isAnnotationPresent(Class annotationClass) method to determine if the annotation described by ToDo.class is present on the method. If so, Method’s T getAnnotation(Class annotationClass) method is called to obtain the annotation.

The ToDo annotations that are processed are those whose types declare a single String value() element (see Listing 5). Because this element’s string-based metadata is comma-separated, it needs to be split into an array of component values. Each of the three component values is then accessed and output.

Compile this source code (javac AnnProcDemo.java). Before you can run the application, you’ll need a suitable class file with @ToDo annotations on its public methods. For example, you could modify Listing 6’s AnnDemo source code to include public in its sort() and search() method headers. You’ll also need Listing 10’s ToDo annotation type, which requires the RUNTIME retention policy.

Listing 10:ToDo.java (version 4)

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 ToDo { String value(); }

Compile the modified AnnDemo.java and Listing 10, and execute the following command to process AnnDemo’s ToDo annotations:

java AnnProcDemo AnnDemo

If all goes well, you should observe the following output:

ID = 1000 Finish date = 10/10/2019 Coder = John Doe ID = 1000 Finish date = 10/10/2019 Coder = John Doe

Processing annotations with apt and the Java compiler

Java 5 a introduit un aptoutil de traitement des annotations de manière généralisée. Java 6 a migré aptles fonctionnalités de son javacoutil de compilation et Java 7 aptest devenu obsolète , qui a ensuite été supprimé (à partir de Java 8).

Types d'annotations standard

En plus Target, Retention, Documentedet Inherited, Java 5 introduit java.lang.Deprecated, java.lang.Overrideet java.lang.SuppressWarnings. Ces trois types d'annotations sont conçus pour être utilisés dans un contexte de compilateur uniquement, c'est pourquoi leurs stratégies de rétention sont définies sur SOURCE.

Obsolète