Обработка аннотаций во время выполнения

Последнее обновление: 01.11.2025

Аннотация сама по себе в языке 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!!!
    
  • Помощь сайту
    Юмани:
    410011174743222
    Номер карты:
    4048415020898850