Суть и принцип реализации Java Annotations (аннотаций) (ниже)

Java

введение

в предыдущей статье"Суть и принцип реализации Java Annotations (аннотации) (Часть 1)", мы представилиJavaСущность, принцип и способ действия аннотаций. В этой статье будет продолжено изучениеJavaАннотированJVMКак реализуется уровень и в сочетании со сценарием приложения реализуетсяJavaаннотация.

Получить данные аннотации

Не забывайте о первоначальном замысле, помните о миссии.

Помните, почему мы сделали аннотацию?Метаданные-данные!

Примечание - это по сути, чтобы принести данные в код.

Как мы видели ранее, данные передаются через аннотированные переменные-члены (много раз сvalue()) Для хранения, то данные приходят, как это использовать?

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

отражение.

Во-первых, давайте взглянем на классы, которые часто используются в отражении Java (Class, Method, Field)Определение:

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
...                                  
}

Вы можете видеть, что класс Class реализованAnnotatedElementэтот интерфейс.

public final class Method extends Executable {
    ...
}
class Field extends AccessibleObject implements Member {
    ...
}

иExecutableунаследовано отAccessibleObject, AccessibleObjectДостигнут**AnnotatedElement**!

Слепой студент, ты нашел Хуадяня?

Классы, обычно используемые в этих отражениях, реализованыAnnotatedElementЭтот интерфейс!

Тогда давайте посмотримAnnotatedElementКакие методы определяет этот интерфейс (jdk 1.8):

public interface AnnotatedElement {
    
    // 是否有注解
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        ...
    }

    // 获取指定类型的注解
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    // 获取所有注解
    Annotation[] getAnnotations();

    // 根据类型获得注解
    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
       ...
     }

    // 获取声明的注解
    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
        ...
     }

    // 通过类型获取声明的注解
    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        ...
    }

    // 获取声明注解列表
    Annotation[] getDeclaredAnnotations();
}

Видно, что этот интерфейс — способ получить аннотацию!

Тогда давайте пошевелим руками и напишем аннотацию на основеHello WorldБар!

Сначала настройте аннотацию, обратите вниманиеRetentionУстановить какRUNTIME:

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target(METHOD)
@Retention(RUNTIME)
public @interface TestAnno {
    String value() default "abc";
}

Наша аннотация используется для модификации метода, и естьvalueзначение свойства, по умолчанию "abc".

затем напишитеmainфункция:

import java.lang.reflect.Method;

public class AnnotationTest {

    @TestAnno("Hello World")
    public static void main(String[] args) {
        try {
            Class cls = AnnotationTest.class;
            Method method = cls.getMethod("main", String[].class);
            TestAnno anno = method.getAnnotation(TestAnno.class);
            System.out.println(anno.value());
        } catch (Exception ignore) {}
    }
}

Сначала получим отражениемmainфункциональныйMethodобъект;

тогда позвониMethodобъектgetAnnotationМетод, как ссылкаTestAnnoтип;

Это позволяет нам войтиmainАннотация, написанная над функцией, а затем вызовvalueфункция получения "Hello World":

Hello World

Process finished with exit code 0

Похоже, вы закончили, не так ли? но,и т.д! Где это, кажется, ах!

Как мы уже говорили, в чем суть аннотации? интерфейс! Не реализован интерфейс, как бы это назватьvalue()способ получить"Hello World" Как насчет?

Этот способ не писать реализацию, а автоматически реализовывать ее через какой-то механизм во время выполнения, знакомые сmybatisодноклассники, вы немного знакомы? Верно! этодинамический-динамический-агент-агент!

Динамические прокси и аннотации

Итак, без лишних слов, давайте проверим, так ли это!

Сначала мыJVMДобавьте эту строку в параметры запуска:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

выслеживатьJVMДинамические прокси-классы, созданные во время выполнения:

Затем запустите вышеуказанную основную функцию, и, наконец, мы увидимcom, открыв слой за слоем, вы найдете несколько папок с именами$Proxy*.classдокумент:

затем откройте$Proxy1.class,посмотрим:

package com.sun.proxy;

import com.source.sourceapi.TestAnno;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy1 extends Proxy implements TestAnno {...}

Затем прокрутите вниз, и вы увидите:

    public final String value() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

Конечно, это сбылосьvalue()метод! Это то, что мы можем получить прямо через интерфейс»Hello World"Причина!

Итак, следующее, что нужно выяснить, это то, как реализован этот динамический прокси.

Учащиеся, знакомые с динамическими агентами, знают, что ядром их реализации является:

    public $Proxy1(InvocationHandler var1) throws  {
       super(var1);
   }

То есть конструкторInvocationHandlerобъект. затем аннотировалHandlerКакая конкретная реализация? ответ:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
   private static final long serialVersionUID = 6182022883658399397L;
   private final Class<? extends Annotation> type;
   private final Map<String, Object> memberValues;
   private transient volatile Method[] memberMethods = null;
   ...
}

этоclassВнутри основных атрибутов, это то, чтоmemberValues, назначения, которые мы назначаем свойствам аннотаций при использовании аннотаций, хранятся в этомmapв.

Оглядываясь назад на реализацию метода value() в нашем прокси-классе, он фактически вызывает метод вызова в обработчике. Метод invoke путем различных логических рассуждений наконец получает нужные метаданные из метода memberValues!

Что касается того, как это делает метод вызова? Эй, пожалуйста, прочитайте исходный код вручную, добро пожаловать на обсуждение в области сообщений!

настроитьValidation

Вот мы наконецJavaРеализация аннотаций полностью выяснена.

Поэтому некоторые студенты хотят спросить, эта вещь очень распространена в рамках, но какая практическая польза в нашей реальной работе?

Далее давайте объединим практический сценарий применения и пошевелим руками!

Знаком сSpring BootУчащиеся, разработавшие разработку, знают, что обычно приходитсяhttpПараметры запроса проверяются на корректность. Например, некоторые поля не могут быть пустыми, а формат некоторых полей должен бытьemailЛа подожди.

Обычно мы используемjavax.validationдля завершения проверки параметров. иvalidationЭто достигается за счет самой аннотации. Например

@NotBlank
@Email
@Future
...

Но ах, мы иногда сталкиваемся с некоторымиvalidationТребования проверки, которые не могут быть выполнены предоставленными аннотациями. Например, при создании пользователя его обычно просят дважды ввести пароль.validationУбедиться, что пароли, введенные дважды, совпадают?

во-первыхНастраиваем аннотацию:

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
@Documented
public @interface FieldMatch {
   String message() default "{constraints.fieldmatch}";

   Class<?>[] groups() default {};

   Class<? extends Payload>[] payload() default {};

   /**
    * @return The first field
    */
   String first();

   /**
    * @return The second field
    */
   String second();

   /**
    * Defines several <code>@FieldMatch</code> annotations on the same element
    *
    * @see FieldMatch
    */
   @Target({TYPE, ANNOTATION_TYPE})
   @Retention(RUNTIME)
   @Documented
   @interface List
   {
       FieldMatch[] value();
   }
}

во-первыхЦель этой аннотации должна быть на уровне TYPE, потому что мы сравниваем два свойства в классе.

ВторойВ этой аннотации мы определяем еще одну аннотацию List, которую можно использовать в форме FieldMath.List. Это связано с тем, что в одном запросе можно сравнивать несколько пар параметров.

НаконецДавайте посмотрим на конкретные свойства в этой аннотации, их три:

  • first(), первый параметр
  • second(), второй параметр
  • message(), какая ошибка возвращается, если совпадения нет

Также обратите внимание, что над этой аннотацией естьConstraintПримечания:@Constraint(validatedBy = FieldMatchValidator.class).

Определение этой аннотации взято изjavax.validation

package javax.validation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraint {
    Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
}

Он используется для идентификации нашей пользовательской аннотацииFieldMatchявляется ограничением проверки. его назначениеvalidatedBy = FieldMatchValidator.classЗатем он показывает, что логика суждения об ограничениях работает.FieldMatchValidatorв этом классе.

Итак, давайте реализуем эту логику дальше:

public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {

    private String firstFieldName;

    private String secondFieldName;

    @Override
    public void initialize(final FieldMatch constraintAnnotation) {
        firstFieldName = constraintAnnotation.first();
        secondFieldName = constraintAnnotation.second();
    }

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context) {

        try {
            final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
            final Object secondObj = BeanUtils.getProperty(value, secondFieldName);

            return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
        }

        catch (final Exception ignore) {
            // ignore
        }
        return true;
    }

}

Сначала этоclassреализуетсяConstraintValidator. Друзья, знакомые с процедурами объектно-ориентированного программирования, должны здесь догадаться.Validationтоже работает. за то, чтоConstraintКлассы или свойства с аннотациями типа, вызывая их соответствующиеvalidationКатегорияisValid()метод, чтобы определить, проходит ли проверка параметра, если нет, выдает исключение с указанным сообщением.

Так это или нет, пожалуйста, внимательно просмотрите код, добро пожаловать на обсуждение в области сообщений!

Суммировать

Мы использовали две статьи, чтобы глубже понятьJavaХарактер, сущность и принцип реализации аннотаций, а также реализовывать аннотации, отвечающие сценариям применения, самостоятельно.

Как видите, аннотации в основном используют методы отражения и динамического прокси.

Освоение аннотаций по-прежнему очень необходимо, потому что многиеJavaФреймворк использует аннотации для передачи метаданных, и освоение принципов аннотаций поможет нам полностью понять и использовать фреймворк, а также может расширить функции фреймворка в соответствии с нашими собственными потребностями. Это потрясающе!