Глубокое понимание аннотаций Java

Java

Содержание этой статьи основано на JDK8. Аннотации появились в JDK5, а в последующих версиях JDK часть содержимого была расширена Аннотации, в которых явно не указана версия в этой статье, уже поддерживаются JDK5.

:notebook: Эта статья была заархивирована в: "blog"

:keyboard: Пример кода в этой статье был заархивирован в: "javacore"

Введение

форма аннотации

В Java аннотации@Модификатор начала персонажа. следующее:

@Override
void mySuperMethod() { ... }

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

@Author(
   name = "Benjamin Franklin",
   date = "3/27/2003"
)
class MyClass() { ... }

Если имеется только одно свойство с именем value, имя можно не указывать, например:

@SuppressWarnings("unchecked")
void myMethod() { ... }

Если аннотация не является атрибутом,标记注解. как:@Override.

что такое аннотации

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

Разбор аннотации часто принимает две формы:

  • Прямое сканирование- Сканирующий компилятор компилятор относится к классу или модифицированному способу для обработки кода Java Commited Bytecode обнаружит некоторые заметки, то он выполнит некоторую обработку по этим аннотациям. Это относится только к встроенным нотам класса JDK.
  • отражение во время выполнения- Если вы хотите настроить аннотацию, компилятор Java не может распознать и обработать эту аннотацию, он может только выбрать, следует ли компилировать ее в файл байт-кода в соответствии с областью действия аннотации. Если вы хотите обрабатывать аннотации, вы должны использовать технологию отражения, чтобы идентифицировать аннотацию и информацию, которую она несет, а затем обрабатывать ее соответствующим образом.

Роль аннотаций

Аннотации имеют множество применений:

  • Информация о компиляторе. Аннотации могут использоваться компилятором для обнаружения ошибок или подавления предупреждений.
  • Обработка во время компиляции и во время развертывания. Программы могут обрабатывать информацию аннотаций для создания кода, XML-файлов и т. д.
  • Обработка времени выполнения - определенные аннотации могут быть проверены и обработаны во время выполнения.

В качестве программиста Java я испытал страх доминирования различных файлов конфигурации (XML, свойства). Слишком много файлов конфигурации могут затруднить удержание проекта. На мой взгляд, использование аннотаций для уменьшения файлов конфигурации или кода является наибольшим использованием аннотаций.

Стоимость аннотации

Во всем есть плюсы и минусы, и то же самое можно сказать и о технологии аннотаций. Существует также цена использования аннотаций:

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

Однако, как говорится, цена, уплаченная аннотациями, приемлема по сравнению с функциями, которые она обеспечивает.

Примечание

Аннотации можно применять к объявлениям классов, полей, методов и других элементов программы.

Начиная с JDK8 область применения аннотаций была дополнительно расширена.Ниже приведены новые области применения:

Выражение инициализации экземпляра класса:

new @Interned MyObject();

Преобразование типа:

myString = (@NonNull String) str;

Объявление реализации интерфейса:

class UnmodifiableList<T> implements
    @Readonly List<@Readonly T> {}

Бросьте оператор исключения:

void monitorTemperature()
    throws @Critical TemperatureException {}

Встроенные аннотации

В JDK встроены следующие аннотации:

  • @Override
  • @Deprecated
  • @SuppressWarnnings
  • @SafeVarargs(Введено в JDK7)
  • @FunctionalInterface(Введено в JDK8)

@Override

@OverrideИспользуется для указания того, что измененный метод переопределяет метод родительского класса.

Если вы попытаетесь использовать@OverrideКогда тег на самом деле не перезаписан метод родительского класса, Compiler Java будет тревожить.

@OverrideПример:

public class OverrideAnnotationDemo {

    static class Person {
        public String getName() {
            return "getName";
        }
    }


    static class Man extends Person {
        @Override
        public String getName() {
            return "override getName";
        }

        /**
         *  放开下面的注释,编译时会告警
         */
       /*
        @Override
        public String getName2() {
            return "override getName2";
        }
        */
    }

    public static void main(String[] args) {
        Person per = new Man();
        System.out.println(per.getName());
    }
}

@Deprecated

@DeprecatedОн используется, чтобы указать, что модифицированный класс или член класса, метод класса устарел, и его не рекомендуется использовать.

@Deprecatedиметь определенныйпреемственность: Если мы используем устаревший класс или член класса в нашем коде путем наследования или переопределения, даже если подкласс или подметод не помечен как@Deprecated, но компилятор все равно предупреждает.

Уведомление:@DeprecatedЭти типы аннотаций и javadoc@deprecatedЭтот тег отличается: первый распознается компилятором java, второй распознается инструментом javadoc для создания документации (включая описание того, почему член программы устарел, как его следует запретить или заменить).

@DeprecatedПример:

public class DeprecatedAnnotationDemo {
    static class DeprecatedField {
        @Deprecated
        public static final String DEPRECATED_FIELD = "DeprecatedField";
    }


    static class DeprecatedMethod {
        @Deprecated
        public String print() {
            return "DeprecatedMethod";
        }
    }


    @Deprecated
    static class DeprecatedClass {
        public String print() {
            return "DeprecatedClass";
        }
    }

    public static void main(String[] args) {
        System.out.println(DeprecatedField.DEPRECATED_FIELD);

        DeprecatedMethod dm = new DeprecatedMethod();
        System.out.println(dm.print());


        DeprecatedClass dc = new DeprecatedClass();
        System.out.println(dc.print());
    }
}
//Output:
//DeprecatedField
//DeprecatedMethod
//DeprecatedClass

@SuppressWarnnings

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

@SuppressWarningНе разметка аннотации. Он имеет типString[]Член массива , в котором хранится тип будильника, который нужно закрыть. Для компилятора javac да-XlintЭффективные варианты предупреждения также для имени@SuppressWaringsДействителен, и компилятор игнорирует нераспознанные имена предупреждений.

@SuppressWarningПример:

@SuppressWarnings({"rawtypes", "unchecked"})
public class SuppressWarningsAnnotationDemo {
    static class SuppressDemo<T> {
        private T value;

        public T getValue() {
            return this.value;
        }

        public void setValue(T var) {
            this.value = var;
        }
    }

    @SuppressWarnings({"deprecation"})
    public static void main(String[] args) {
        SuppressDemo d = new SuppressDemo();
        d.setValue("南京");
        System.out.println("地名:" + d.getValue());
    }
}

@SuppressWarningsКраткое описание общих значений параметров для аннотаций:

  • deprecation- предупреждения при использовании устаревших классов или методов;
  • unchecked- предупреждения при выполнении непроверенных преобразований, например, при использовании коллекций без использования Generics для указания типа коллекции;
  • fallthrough- предупреждение, когда блок Switch ведет прямо к следующему кейсу без Break;
  • path- предупреждения при наличии несуществующих путей в пути к классам, путях к исходным файлам и т.д.;
  • serial- Предупреждение об отсутствии определения SerialVersionuid в сериализованном классе;
  • finally- предупреждение, когда любое предложение finally не может быть завершено нормально;
  • all- Все предупреждения.
@SuppressWarnings({"uncheck", "deprecation"})
public class InternalAnnotationDemo {

    /**
     * @SuppressWarnings 标记消除当前类的告警信息
     */
    @SuppressWarnings({"deprecation"})
    static class A {
        public void method1() {
            System.out.println("call method1");
        }

        /**
         * @Deprecated 标记当前方法为废弃方法,不建议使用
         */
        @Deprecated
        public void method2() {
            System.out.println("call method2");
        }
    }

    /**
     * @Deprecated 标记当前类为废弃类,不建议使用
     */
    @Deprecated
    static class B extends A {
        /**
         * @Override 标记显示指明当前方法覆写了父类或接口的方法
         */
        @Override
        public void method1() { }
    }

    public static void main(String[] args) {
        A obj = new B();
        obj.method1();
        obj.method2();
    }
}

@SafeVarargs

@SafeVarargsПредставлен в JDK7.

@SafeVarargsРоль: сообщает компилятору, что дженерики в параметрах переменной длины являются типобезопасными. Параметры переменной длины хранятся с использованием массивов, а массивы и дженерики плохо сочетаются.

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

@SafeVarargsПримечания используют:

  • @SafeVarargsАннотации можно использовать в конструкторах.
  • @SafeVarargsАннотации можно использовать дляstaticилиfinalметод.

@SafeVarargsПример:

public class SafeVarargsAnnotationDemo {
    /**
     * 此方法实际上并不安全,不使用此注解,编译时会告警
     */
    @SafeVarargs
    static void wrongMethod(List<String>... stringLists) {
        Object[] array = stringLists;
        List<Integer> tmpList = Arrays.asList(42);
        array[0] = tmpList; // 语法错误,但是编译不告警
        String s = stringLists[0].get(0); // 运行时报 ClassCastException
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");

        List<String> list2 = new ArrayList<>();
        list.add("1");
        list.add("2");

        wrongMethod(list, list2);
    }
}

Приведенный выше код, если не используется@SafeVarargs, он предупредит при компиляции

[WARNING] /D:/Codes/ZP/Java/javacore/codes/basics/src/main/java/io/github/dunwu/javacore/annotation/SafeVarargsAnnotationDemo.java: 某些输入文件使用了未经检查或不安全的操作。
[WARNING] /D:/Codes/ZP/Java/javacore/codes/basics/src/main/java/io/github/dunwu/javacore/annotation/SafeVarargsAnnotationDemo.java: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

@FunctionalInterface

@FunctionalInterfaceВведен в JDK8.

@FunctionalInterfaceИспользуется для указания того, что декорируемый интерфейс является функциональным интерфейсом.

Следует отметить, что если интерфейс соответствует определению «функциональный интерфейс»,@FunctionalInterfaceЭто нормально, но если вы не пишете функциональный интерфейс, используйте@FunctionInterface, то компилятор сообщит об ошибке.

Что такое функциональный интерфейс?

Функциональный интерфейс (Functional Interface) — это один и только один абстрактный метод, но может быть множество неабстрактных методов интерфейса. Функциональный интерфейс можно неявно преобразовать в лямбда-выражение.

Особенности функционального интерфейса:

  • Интерфейс имеет и может иметь только один абстрактный метод (абстрактный метод имеет только определение метода, без тела метода).
  • Вы не можете переопределить публичный метод в классе Object в интерфейсе (компилятор сообщит об ошибке, если вы его напишете).
  • Разрешены реализации по умолчанию.

Пример:

public class FunctionalInterfaceAnnotationDemo {

    @FunctionalInterface
    public interface Func1<T> {
        void printMessage(T message);
    }

    /**
     * @FunctionalInterface 修饰的接口中定义两个抽象方法,编译时会报错
     * @param <T>
     */
    /*@FunctionalInterface
    public interface Func2<T> {
        void printMessage(T message);
        void printMessage2(T message);
    }*/

    public static void main(String[] args) {
        Func1 func1 = message -> System.out.println(message);
        func1.printMessage("Hello");
        func1.printMessage(100);
    }
}

метааннотация

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

Роль мета-аннотаций заключается в определении других аннотаций..

В Java доступны следующие типы метааннотаций:

  • @Retention
  • @Target
  • @Documented
  • @Inherited(Введено в JDK8)
  • @Repeatable(Введено в JDK8)

Эти типы и классы они поддерживаютjava.lang.annotationможно найти в упаковке. Давайте посмотрим на роль каждой мета-аннотации и инструкции по использованию соответствующих подпараметрических параметров.

@Retention

@RetentionУказывает уровень хранения аннотации.

@RetentionИсходный код:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

RetentionPolicyтип перечисления, определяющий@RetentionУровни хранения, поддерживаемые оформленными аннотациями:

  • RetentionPolicy.SOURCE- Отмеченные аннотации допустимы только в исходных файлах и игнорируются компилятором.
  • RetentionPolicy.CLASS- Отмеченные аннотации допустимы в файлах классов и будут игнорироваться JVM.
  • RetentionPolicy.RUNTIME- Отмеченные аннотации действительны во время выполнения.

@RetentionПример:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField";
    public boolean defaultDBValue() default false;
}

@Documented

@DocumentedУказывает, что Javadoc следует использовать всякий раз, когда используется указанная аннотация (по умолчанию аннотации не включаются в Javadoc). Для получения дополнительной информации см.:Javadoc tools page.

@DocumentedПример:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField";
    public boolean defaultDBValue() default false;
}

@Target

@TargetУказывает типы элементов, которые может украшать аннотация.

@TargetИсходный код:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

ElementTypeтип перечисления, определяющий@TargetИзмененные аннотации могут быть применены к:

  • ElementType.ANNOTATION_TYPE- Отмеченные аннотации можно применять к типам аннотаций.
  • ElementType.CONSTRUCTOR- Отмеченные аннотации можно применять к конструкторам.
  • ElementType.FIELD- Отмеченные аннотации можно применять к полям или свойствам.
  • ElementType.LOCAL_VARIABLE- Отмеченные аннотации можно применять к локальным переменным.
  • ElementType.METHOD- К методам можно применять отмеченные аннотации.
  • ElementType.PACKAGE- отмеченные аннотации могут быть применены к декларации пакетов.
  • ElementType.PARAMETER- отмеченные аннотации могут быть применены к параметрам метода.
  • ElementType.TYPE- Отмеченные аннотации можно применять к любому элементу класса.

@TargetПример:

@Target(ElementType.TYPE)
public @interface Table {
    /**
     * 数据表名称注解,默认值为类名称
     * @return
     */
    public String tableName() default "className";
}

@Target(ElementType.FIELD)
public @interface NoDBColumn {}

@Inherited

@InheritedУказывает, что тип аннотации может быть унаследован (не по умолчанию).

Указывает на автоматическое наследование типов аннотаций. Если объявление типа аннотации существует@InheritedЗадержанные аннотации все подклассы модифицированного класса наследуют эту аннотацию.

Уведомление:@InheritedТипы аннотаций наследуются подклассами аннотированного класса. Класс не наследует аннотации от интерфейсов, которые он реализует, а методы не наследуют аннотации от методов, которые он переопределяет.

Кроме того, когда@InheritedАннотировано аннотациями типа@RetentionдаRetentionPolicy.RUNTIME, API отражения расширяет возможности этого наследования. если мы используемjava.lang.reflectрасспросить об одном@InheritedКогда типы примечаний, проверки начнут код отражения работы: проверка класса и его родитель, пока обнаружение указанного типа аннотации не обнаружено, или на верхний этаж класса наследования структуры.

@Inherited
public @interface Greeting {
    public enum FontColor{ BULE,RED,GREEN};
    String name();
    FontColor fontColor() default FontColor.GREEN;
}

@Repeatable

@RepeatableУказывает, что аннотацию можно использовать повторно.

с весной@ScheduledНапример:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Schedules {
	Scheduled[] value();
}

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
  // ...
}

Пример приложения:

public class TaskRunner {

    @Scheduled("0 0/15 * * * ?")
    @Scheduled("0 0 12 * ?")
    public void task1() {}
}

пользовательская аннотация

использовать@interfaceКогда пользовательские аннотации автоматически наследуютсяjava.lang.annotation.AnnotationИнтерфейс, остальные детали выполняются автоматически компилятором. При определении аннотации другие аннотации или интерфейсы не могут наследоваться.@interfaceИспользуется для объявления аннотации, где каждый метод фактически объявляет параметр конфигурации. Имя метода — это имя параметра, а тип возвращаемого значения — тип параметра (тип возвращаемого значения может быть только базовыми типами, Class, String, enum). в состоянии пройтиdefaultобъявить значения по умолчанию для параметров.

Здесь я реализуюRegexValidОбычный инструмент проверки аннотаций, показывающий полные шаги пользовательской аннотации.

1. Определение аннотаций

Синтаксис аннотаций следующий:

public @interface 注解名 {定义体}

Давайте определим аннотацию:

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {}

инструкция:

Через предыдущий раздел для мета-аннотаций@Target,@Retention,@DocumentedЗдесь легко понять.

  • Приведенный выше код определяет@RegexValidаннотации.
  • @Documentedвыражать@RegexValidследует использовать javadoc.
  • @Target({ElementType.FIELD, ElementType.PARAMETER})выражать@RegexValidМожет быть оформлен на членах класса или параметрах метода.
  • @Retention(RetentionPolicy.RUNTIME) означает@RegexValidДействует во время выполнения.

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

2. Свойства аннотации

Синтаксис атрибута аннотации следующий:

[访问级别修饰符] [数据类型] 名称() default 默认值;

Например, мы хотим определить строковое свойство с именем value в аннотации, его значение по умолчанию — пустая строка, а уровень доступа — уровень по умолчанию, тогда его следует определить следующим образом:

String value() default "";

Уведомление:В аннотации, когда мы определяем атрибут, необходимо добавить имя атрибута.().

Определение свойств аннотации имеет следующие точки:

  • Атрибуты аннотации можно использовать толькоpublicили уровень доступа по умолчанию (т.е. без указания модификатора уровня доступа) оформленный.

  • Существуют ограничения на тип данных атрибута аннотации.. Поддерживаются следующие типы данных:

    • Все основные типы данных (byte, char, short, int, long, float, double, boolean)
    • Тип строки
    • класс класс
    • тип перечисления
    • Тип аннотации
    • Массивы всех вышеперечисленных типов
  • Атрибут аннотации должен иметь определенное значение, рекомендуется указать значение по умолчанию. Свойства аннотации можно указать, только указав значение по умолчанию или при использовании аннотации, что является более надежным способом указать значение по умолчанию. Если свойство аннотации является ссылочным типом, оно не может иметь значение null. Это ограничение затрудняет для процессора аннотаций определение того, является ли атрибут аннотации значением по умолчанию или значением атрибута, указанным при использовании аннотации. По этой причине, когда мы устанавливаем значения по умолчанию, мы обычно определяем некоторые специальные значения, такие как пустые строки или отрицательные числа.

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

// 这两种方式效果相同
@RegexValid("^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
@RegexValid(value = "^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")

Пример:

Знание основных моментов определения аннотационных атрибутов, давайте@RegexValidАннотация определяет несколько свойств.

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {
    enum Policy {
        // @formatter:off
        EMPTY(null),
        DATE("^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\\1"
            + "(?:29|30)|(?:0?[13578]|1[02])\\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|"
            + "(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\\2(?:29))$"),
        MAIL("^[A-Za-z0-9](([_\\.\\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\\.\\-]?[a-zA-Z0-9]+)*)\\.([A-Za-z]{2,})$");
        // @formatter:on

        private String policy;

        Policy(String policy) {
            this.policy = policy;
        }

        public String getPolicy() {
            return policy;
        }
    }

    String value() default "";
    Policy policy() default Policy.EMPTY;
}

инструкция:

В приведенном выше примере кода мы определили два свойства аннотации:Stringатрибут value типа иPolicyАтрибут политики типа перечисления.PolicyВ перечислении определены несколько регулярных выражений по умолчанию, которые предназначены для непосредственного использования этих общих выражений для регулярной проверки. Учитывая, что нам может потребоваться передать некоторые пользовательские регулярные выражения для проверки других сценариев, атрибут value определен, чтобы позволить пользователям передавать регулярные выражения.

Уже,@RegexValidзаявление закончилось. Однако программа по-прежнему не знает, что делать.@RegexValidэта аннотация. Нам также необходимо определить процессоры аннотаций.

3. Обработчик аннотаций

Аннотации не более полезны, чем аннотации, если нет возможности и труда их прочитать. Важной частью использования аннотаций является создание процессора аннотаций. JDK5 расширяет API механизма отражения, чтобы помочь программистам быстро создавать собственные процессоры аннотаций.

java.lang.annotation.AnnotationЭто интерфейс.Программа может получить объект аннотации указанного элемента программы посредством отражения, а затем получить метаданные в аннотации через объект аннотации..

AnnotationИсходный код интерфейса выглядит следующим образом:

public interface Annotation {
    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

Кроме того, Java поддерживаетИнтерфейс процессора аннотацийjava.lang.reflect.AnnotatedElement, интерфейс представляет элементы программы в программе, которые могут принимать аннотации.Интерфейс в основном имеет следующие классы реализации:

  • Class- определение класса
  • Constructor- Определение конструктора
  • Field- Усталые определения переменных участников
  • Method- определение метода класса
  • Package- определение пакета класса

java.lang.reflectПакет в основном содержит некоторые классы инструментов, которые реализуют функцию отражения. По факту,java.lang.reflectAPI отражения, предоставляемый пакетом, расширяет возможности чтения аннотаций во время выполнения. Когда тип аннотации определен как аннотация времени выполнения, аннотация может быть видна во время выполнения, а аннотация, сохраненная в файле класса при загрузке файла класса, будет прочитана виртуальной машиной.AnnotatedElementИнтерфейс является родительским интерфейсом всех элементов программы (класса, метода и конструктора), поэтому программа получаетAnnotatedElementПосле объекта программа может вызвать следующие четыре метода объекта для доступа к аннотационной информации:

  • getAnnotation- Возвращает аннотацию указанного типа, которая существует в этом программном элементе, или null, если аннотация типа не существует.
  • getAnnotations- Возвращает все аннотации, которые существуют на этом элементе программы.
  • isAnnotationPresent- Определяет, содержит ли программный элемент аннотацию указанного типа, возвращает true, если она существует, иначе возвращает false.
  • getDeclaredAnnotations- Возвращает все аннотации, существующие непосредственно в этом элементе. В отличие от других методов в этом интерфейсе, этот метод игнорирует унаследованные аннотации. (Если непосредственно для этого элемента не существует аннотации, возвращается массив нулевой длины.) Вызывающие этот метод могут свободно изменять возвращаемый массив по своему желанию; это не влияет на массивы, возвращаемые другими вызывающими объектами.

Зная вышеизложенное, реализуем@RegexValidПроцессор аннотаций для:

import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexValidUtil {
    public static boolean check(Object obj) throws Exception {
        boolean result = true;
        StringBuilder sb = new StringBuilder();
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 判断成员是否被 @RegexValid 注解所修饰
            if (field.isAnnotationPresent(RegexValid.class)) {
                RegexValid valid = field.getAnnotation(RegexValid.class);

                // 如果 value 为空字符串,说明没有注入自定义正则表达式,改用 policy 属性
                String value = valid.value();
                if ("".equals(value)) {
                    RegexValid.Policy policy = valid.policy();
                    value = policy.getPolicy();
                }

                // 通过设置 setAccessible(true) 来访问私有成员
                field.setAccessible(true);
                Object fieldObj = null;
                try {
                    fieldObj = field.get(obj);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                if (fieldObj == null) {
                    sb.append("\n")
                        .append(String.format("%s 类中的 %s 字段不能为空!", obj.getClass().getName(), field.getName()));
                    result = false;
                } else {
                    if (fieldObj instanceof String) {
                        String text = (String) fieldObj;
                        Pattern p = Pattern.compile(value);
                        Matcher m = p.matcher(text);
                        result = m.matches();
                        if (!result) {
                            sb.append("\n").append(String.format("%s 不是合法的 %s !", text, field.getName()));
                        }
                    } else {
                        sb.append("\n").append(
                            String.format("%s 类中的 %s 字段不是字符串类型,不能使用此注解校验!", obj.getClass().getName(), field.getName()));
                        result = false;
                    }
                }
            }
        }

        if (sb.length() > 0) {
            throw new Exception(sb.toString());
        }
        return result;
    }
}

инструкция:

Для процессора аннотации в приведенном выше примере этапы выполнения следующие:

  1. Получите все элементы входящего объекта с помощью метода отражения getDeclaredFields.
  2. Пройдитесь по элементам и используйте isAnnotationPresent, чтобы определить, изменен ли элемент указанной аннотацией, если нет, пропустите ее напрямую.
  3. Если элемент изменен аннотацией, передайтеRegexValid valid = field.getAnnotation(RegexValid.class);Эта форма получена, аннотирована для создания экземпляра объекта, а затем вы можете использоватьvalid.value()илиvalid.policy()Эта форма получает значение атрибута, установленное в аннотации.
  4. Логическая обработка выполняется в соответствии со значением атрибута.

4. Используйте аннотации

После завершения вышеуказанной работы мы можем использовать пользовательские аннотации, примеры следующие:

public class RegexValidDemo {
    static class User {
        private String name;
        @RegexValid(policy = RegexValid.Policy.DATE)
        private String date;
        @RegexValid(policy = RegexValid.Policy.MAIL)
        private String mail;
        @RegexValid("^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
        private String phone;

        public User(String name, String date, String mail, String phone) {
            this.name = name;
            this.date = date;
            this.mail = mail;
            this.phone = phone;
        }

        @Override
        public String toString() {
            return "User{" + "name='" + name + '\'' + ", date='" + date + '\'' + ", mail='" + mail + '\'' + ", phone='"
                + phone + '\'' + '}';
        }
    }

    static void printDate(@RegexValid(policy = RegexValid.Policy.DATE) String date){
        System.out.println(date);
    }

    public static void main(String[] args) throws Exception {
        User user = new User("Tom", "1990-01-31", "xxx@163.com", "18612341234");
        User user2 = new User("Jack", "2019-02-29", "sadhgs", "183xxxxxxxx");
        if (RegexValidUtil.check(user)) {
            System.out.println(user + "正则校验通过");
        }
        if (RegexValidUtil.check(user2)) {
            System.out.println(user2 + "正则校验通过");
        }
    }
}

резюме

использованная литература