Как описать код Java с помощью аннотаций

Вы, вероятно, сталкивались с ситуациями, когда вам нужно связать метаданные (данные, описывающие другие данные) с классами, методами и / или другими элементами приложения. Например, вашей группе программирования может потребоваться определить незаконченные классы в большом приложении. Для каждого незавершенного класса метаданные, вероятно, будут включать имя разработчика, ответственного за завершение класса, и ожидаемую дату завершения класса.

До Java 5 комментарии были единственным гибким механизмом, который Java предлагала для связывания метаданных с элементами приложения. Однако комментарии - плохой выбор. Поскольку компилятор их игнорирует, комментарии недоступны во время выполнения. И даже если бы они были доступны, текст нужно было бы проанализировать, чтобы получить важные элементы данных. Без стандартизации того, как указываются элементы данных, их анализ может оказаться невозможным.

загрузить Получить код Загрузите исходный код для примеров в этом руководстве по Java 101. Создано Джеффом Фризеном для.

Нестандартные механизмы аннотации

Java предоставляет нестандартные механизмы для связывания метаданных с элементами приложения. Например, transientзарезервированное слово позволяет аннотировать (связывать данные) поля, которые должны быть исключены во время сериализации.

В Java 5 все изменилось, введены аннотации - стандартный механизм связывания метаданных с различными элементами приложения. Этот механизм состоит из четырех компонентов:

  • @interfaceМеханизм для объявления типов аннотаций.
  • Типы метааннотаций, которые можно использовать для идентификации элементов приложения, к которым применяется тип аннотации; для определения времени жизни аннотации (экземпляра типа аннотации); и больше.
  • Поддержка обработки аннотаций через расширение Java Reflection API (будет обсуждаться в будущей статье), которое вы можете использовать для обнаружения аннотаций времени выполнения программы, и универсальный инструмент для обработки аннотаций.
  • Стандартные типы аннотаций.

Я объясню, как использовать эти компоненты, по мере прохождения этой статьи.

Объявление типов аннотаций с помощью @interface

Вы можете объявить тип аннотации, указав @символ, за которым сразу следует interfaceзарезервированное слово и идентификатор. Например, в листинге 1 объявляется простой тип аннотации, который вы можете использовать для аннотирования поточно-ориентированного кода.

Листинг 1:ThreadSafe.java

public @interface ThreadSafe {}

После объявления этого типа аннотации добавьте к методам, которые вы считаете потокобезопасными, префиксы экземпляров этого типа, добавив @сразу после имени типа к заголовкам методов. В листинге 2 представлен простой пример main()аннотирования метода @ThreadSafe.

Листинг 2:AnnDemo.java (версия 1)

открытый класс AnnDemo {@ThreadSafe public static void main (String [] args) {}}

ThreadSafeэкземпляры не предоставляют никаких метаданных, кроме имени типа аннотации. Однако вы можете предоставить метаданные, добавляя элементы к этому типу, где элемент - это заголовок метода, помещенный в тело типа аннотации.

Помимо того, что элементы кода не имеют тела, на них распространяются следующие ограничения:

  • Заголовок метода не может объявлять параметры.
  • Заголовок метода не может содержать предложение throws.
  • Тип возвращаемого значения методы Header должен быть примитивным типа (например, int), java.lang.String, java.lang.Class, перечисление, типа аннотаций или массив одного из этих типов. Для возвращаемого типа нельзя указать другой тип.

В качестве другого примера в листинге 3 представлен ToDoтип аннотации с тремя элементами, идентифицирующими конкретное задание по кодированию, с указанием даты, когда задание должно быть завершено, и с указанием кодировщика, ответственного за выполнение задания.

Листинг 3:ToDo.java (версия 1)

общедоступный @interface ToDo {int id (); Строка finishDate (); String coder () по умолчанию "н / д"; }

Обратите внимание, что каждый элемент не объявляет никаких параметров или предложения throw, имеет допустимый тип возврата ( intили String) и заканчивается точкой с запятой. Кроме того, последний элемент показывает, что можно указать возвращаемое значение по умолчанию; это значение возвращается, когда аннотация не присваивает значение элементу.

В листинге 4 ToDoаннотируется незавершенный метод класса.

Листинг 4:AnnDemo.java (версия 2)

открытый класс AnnDemo {public static void main (String [] args) {String [] cities = {"Нью-Йорк", "Мельбурн", "Пекин", "Москва", "Париж", "Лондон"}; сортировка (города); } @ToDo (id = 1000, finishDate = "10/10/2019", coder = "John Doe") статическая сортировка по пустоте (Object [] объекты) {}}

В листинге 4 каждому элементу назначается элемент метаданных; например, 1000назначается id. В отличие coder, что idи finishDateэлементы должны быть указаны; в противном случае компилятор сообщит об ошибке. Если coderне присвоено значение, оно принимает "n/a"значение по умолчанию .

Java предоставляет специальный String value()элемент, который можно использовать для возврата списка элементов метаданных, разделенных запятыми. В листинге 5 показан этот элемент в отредактированной версии ToDo.

Листинг 5:ToDo.java (версия 2)

общедоступный @interface ToDo {String value (); }

Когда value()это единственный элемент типа аннотации, вам не нужно указывать valueи =оператор присваивания при присвоении строки этому элементу. В листинге 6 показаны оба подхода.

Листинг 6:AnnDemo.java (версия 3)

открытый класс AnnDemo {public static void main (String [] args) {String [] cities = {"Нью-Йорк", "Мельбурн", "Пекин", "Москва", "Париж", "Лондон"}; сортировка (города); } @ToDo (value = "1000,10 / 10/2019, John Doe") static void sort (Object [] objects) {} @ToDo ("1000,10 / 10/2019, John Doe") static boolean search ( Object [] объекты, ключ объекта) {return false; }}

Использование типов метааннотаций - проблема гибкости

Вы можете аннотировать типы (например, классы), методы, локальные переменные и многое другое. Однако такая гибкость может быть проблематичной. Например, вы можете захотеть ограничиться ToDoтолько методами, но ничто не мешает использовать его для аннотирования других элементов приложения, как показано в листинге 7.

Листинг 7:AnnDemo.java (версия 4)

@ToDo ("1000,10 / 10/2019, John Doe") открытый класс AnnDemo {public static void main (String [] args) {@ToDo (value = "1000,10 / 10/2019, John Doe") String [] cities = {"Нью-Йорк", "Мельбурн", "Пекин", "Москва", "Париж", "Лондон"}; сортировка (города); } @ToDo (value = "1000,10 / 10/2019, John Doe") static void sort (Object [] objects) {} @ToDo ("1000,10 / 10/2019, John Doe") static boolean search ( Object [] объекты, ключ объекта) {return false; }}

В листинге 7 ToDoон также используется для аннотирования AnnDemoкласса и citiesлокальной переменной. Наличие этих ошибочных аннотаций может сбить с толку кого-то, кто просматривает ваш код или даже ваши собственные инструменты обработки аннотаций. На тот случай, когда вам нужно сузить гибкость типа аннотации, Java предлагает этот Targetтип аннотации в своем java.lang.annotationпакете.

Targetявляется типом метааннотации  - типом аннотации, аннотации которого аннотируют типы аннотаций, в отличие от неметааннотационного типа, аннотации которого аннотируют элементы приложения, такие как классы и методы. Он определяет типы элементов приложения, к которым применим тип аннотации. Эти элементы обозначаются элементом Targets ElementValue[] value().

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 появился aptинструмент для обработки аннотаций в обобщенном виде. Java 6 перенесла aptфункциональность в свой javacинструмент компиляции, а Java 7 устарела apt, которая впоследствии была удалена (начиная с Java 8).

Стандартные типы аннотаций

Наряду с Target, Retention, Documentedи Inherited, Java 5 введены java.lang.Deprecated, java.lang.Overrideи java.lang.SuppressWarnings. Эти три типа аннотаций предназначены для использования только в контексте компилятора, поэтому для их политик хранения установлено значение SOURCE.

Не рекомендуется