Аннотация сама по себе в языке Java ничего делает. Чтобы использовать аннотацию каким-то образом, нам нужно ее обрабатывать. Во время выполнения мы можем получить данные аннотаций с помощью рефлексии. Для этого в пакете java.lang.reflect определен интерфейс AnnotatedElement. ДЛя работы с аннотациями этот интерфейс объявляет ряд методов:
T getAnnotation(Class<T>) T getDeclaredAnnotation(Class<T>) T[] getAnnotationsByType(Class<T>) T[] getDeclaredAnnotationsByType(Class<T>) Annotation[] getAnnotations() Annotation[] getDeclaredAnnotations() boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
Рассматривая эти методы, стоит отметить о различиях в аннотациях:
аннотация к элементу типа - это аннотация объявления (declaration annotation).
Для из получения предназначены методы getDeclaredAnnotation() / getDeclaredAnnotations() / getDeclaredAnnotationByType()
аннотация к типу (type annotation) - это аннотация типа. Для их получения предназначены методы с суффиксом ByType.
Методы getAnnotations() и getDeclaredAnnotations() возвращают вообще все применяемые к типу или его элементу аннотации в виде массива объектов Annotation. Этот тип
представляет интерфейс, из методов которого можно отметить метод annotationType() - он возвращает класс конкретной аннотации:
Class<? extends Annotation> annotationType()
Поскольку классы Class, Field, Parameter, Method, Constructor и Package реализуют этот интерфейс, то, получив с помощью рефлексии
определенный компонент программы, мы можем получить его аннотации.
Рассмотрим небольшой пример:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) // Аннотация доступна во время выполнения
@Target(ElementType.TYPE) // Аннотация применяется к типам
@interface MyAnnotation {
String value();
}
// применяем аннотацию
@MyAnnotation(value="Thing")
class Thing{
String name;
}
class Program{
public static void main(String[] args) throws Exception{
var obj = new Thing();
var cl = obj.getClass();
// получаем аннотацию MyAnnotation
MyAnnotation annot = cl.getAnnotation(MyAnnotation.class);
System.out.println(annot); // @MyAnnotation("Thing")
if (annot != null){
System.out.println(annot.value()); // Thing
}
}
}
Здесь сначала определяем переменную класса, к которому применяется аннотация:
var obj = new Thing();
Получаем класс переменной:
var cl = obj.getClass();
И далее получаем нашу аннотацию MyAnnotation:
MyAnnotation annot = cl.getAnnotation(MyAnnotation.class);
Обратите внимание, что в метод передается объект класса аннотации (в данном случае MyAnnotation.class). В качестве результата метод возвращает объект некоторого прокси-класса,
который реализует интерфейс MyAnnotation.
Затем можно вызвать методы интерфейса. Однако может быть, что у класса отсутствует данная аннотация, и в этом случае метод getAnnotation() возвращает null.
Соответственно перед вызовом методов аннотации необходимо проверить ее объект на null:
if (annot != null){
System.out.println(annot.value()); // Thing
}
Метод getAnnotation() возвращает один объект аннотации. Если же к типу или его элементу применяется несколько аннотаций, и все их надо получить,
то можно использовать метод getAnnotations(), который возвращает массив объектов Annotation:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Annotation;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface Description {
String data();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface Name {
String value();
}
@Name(value="Thing")
@Description(data="Represents some object with a name")
class Thing{
String name;
}
class Program{
public static void main(String[] args) throws Exception{
var obj = new Thing();
var cl = obj.getClass();
// получаем все аннотации
Annotation[] annotations = cl.getAnnotations();
for(var annot: annotations){
System.out.println(annot);
// получаем конкретный тип аннотации
System.out.println(annot.annotationType());
}
}
}
В данном случае к классу Thing применяется две аннотации, и мы получим следующий консольный вывод:
@Name("Thing")
interface Name
@Description(data="Represents some object with a name")
interface Description
Рассмотрим более практический пример применения аннотаций. Для этого возьмем следующую программу:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Range {
int min();
int max();
}
class Person{
String name;
@Range(min=1, max=110)
int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
String getName(){ return name; }
int getAge(){ return age; }
}
class RangeValidator{
public static boolean validate(Object obj) throws Exception{
// Получаем класс объекта
var cl = obj.getClass();
// Проверяем аннотации на уровне полей
for (Field field : cl.getDeclaredFields()) {
// если к полю применяется аннотация Range
if (field.isAnnotationPresent(Range.class) && field.getType()==int.class) {
// получаем аннотацию
Range range = field.getAnnotation(Range.class);
// делаем поле доступным для получения его значения
field.setAccessible(true);
// получаем значение поля
int age = (int)field.get(obj);
// проверяем значение поля
if(age > range.max() || age < range.min()) return false;
}
}
return true;
}
}
class Program{
public static void main(String[] args) throws Exception{
Person tom = new Person("Tom", 41);
validatePerson(tom); // Person Tom is valid
Person bob = new Person("Bob", 141);
validatePerson(bob); // Person Bob is invalid!!!
}
static void validatePerson(Person p) throws Exception {
if(RangeValidator.validate(p)) System.out.printf("Person %s is valid\n", p.getName());
else System.out.printf("Person %s is invalid!!!\n", p.getName());
}
}
Данная программа создает механизм, который позволяет декларативно задавать правила для полей, а затем динамически проверять, соответствуют ли реальные объекты этим правилам. Рассмотрим пошагово, что здесь происходит:
Создание аннотации @Range
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Range {
int min();
int max();
}
Аннотация имеет два параметра, min и max, которые будут хранить допустимый диапазон значений.
Класс Person имеет поле age, которое аннотировано созданной нами аннотацией:
class Person{
String name;
@Range(min=1, max=110)
int age;
......................
}
Это "правило" означает, что допустимый возраст для Person должен быть в диапазоне от 1 до 110.
Класс RangeValidator отвечает за проверку произвольного объекта. Для этого в нем определен метод validate(), который принимает любой объект obj
и возвращает true (если объект валиден) или false (если нет).
public static boolean validate(Object obj) throws Exception{
// Получаем класс объекта
var cl = obj.getClass();
// Проверяем аннотации на уровне полей
for (Field field : cl.getDeclaredFields()) {
// если к полю применяется аннотация Range
if (field.isAnnotationPresent(Range.class) && field.getType()==int.class) {
// получаем аннотацию
Range range = field.getAnnotation(Range.class);
// делаем поле доступным для получения его значения
field.setAccessible(true);
// получаем значение поля
int age = (int)field.get(obj);
// проверяем значение поля
if(age > range.max() || age < range.min()) return false;
}
}
return true;
}
С помощью рефлексии метод получает "описание" класса переданного объекта (например, класса Person). Далее перебираем все поля класса
for (Field field : cl.getDeclaredFields())
Затем проверяем, есть ли у текущего поля аннотация @Range
if (field.isAnnotationPresent(Range.class) ...)
Если аннотация найдена, то получаем саму аннотацию и ее параметры ( min=1 и max=110).
Range range = field.getAnnotation(Range.class);
Разрешаем доступ к полю, даже если бы оно было private:
field.setAccessible(true);
С помощью рефлексии получаем реальное значение поля age из конкретного объекта
int age = (int)field.get(obj);
И в конце сравниваем значение поля с границами из аннотации (1 и 110). Если значение выходит за рамки, метод немедленно возвращает false (объект невалиден).
if(age > range.max() || age < range.min()) return false;
В противном случае возвращается true - объект считается валидным.
В классе Program для упрощени проверки определяет вспомогательный метод validatePerson и вызываем его, передавая в него пару типовых объектов.
Консольный вывод программ:
Person Tom is valid Person Bob is invalid!!!