В прошлом "XML" предпочитали основные фреймворки. Он выполнял почти все конфигурации в фреймворке слабо связанным образом. Однако по мере того, как проект становился все больше и больше, содержание "XML" становилось все более и более сложным, и стоимость обслуживания становится высокой.
Итак, кто-то предложил маркированный и сильно связанный метод конфигурации, «аннотацию». Методы могут быть аннотированы, классы также могут быть аннотированы, а атрибуты полей также могут быть аннотированы.
Два разных режима конфигурации "аннотация" и "XML" обсуждались в течение многих лет, и каждый из них имеет свои преимущества и недостатки. Аннотации могут обеспечить большее удобство, их легко поддерживать и модифицировать, но они имеют высокую степень связанности, в то время как XML относительно Аннотации противоположны.
Стремление к низкой связанности означает отказ от высокой эффективности, а погоня за эффективностью неизбежно натолкнется на связанность. Цель этой статьи — больше не проводить различие между ними, а представить основное содержание, связанное с аннотациями, на простейшем языке.
Характер аннотаций
В интерфейсе "java.lang.annotation.Annotation" есть такое предложение для описания "аннотации".
The common interface extended by all annotation types
Все типы аннотаций наследуются от этого общего интерфейса (аннотаций).
Это предложение немного абстрактно, но оно передает суть аннотации. Давайте посмотрим на определение встроенной аннотации JDK:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Это определение аннотации @Override, которая по сути:
public interface Override extends Annotation{
}
Да, суть аннотации — это интерфейс, который наследует интерфейс Annotation. Для этого вы можете декомпилировать любой аннотированный класс и вы получите результат.
Аннотация — это просто особый вид аннотации в точном смысле, и если нет кода для ее разбора, она может быть даже не так хороша, как аннотация.
Аннотации для синтаксического анализа класса или метода часто имеют две формы: одна — прямое сканирование во время компиляции, а другая — отражение во время выполнения. Об отражении мы поговорим позже, а сканирование компилятора означает, что компилятор обнаружит, что класс или метод модифицирован некоторыми аннотациями в процессе компиляции байт-кода java-кода, а затем обнаружит эти аннотации. некоторая обработка.
Типичной является аннотация @Override.Как только компилятор обнаружит, что метод украшен аннотацией @Override, компилятор проверит, действительно ли сигнатура метода текущего метода переопределяет метод родительского класса, то есть сравнивает родительский класс , имеют одинаковую сигнатуру метода.
Эта ситуация применима только к тем классам аннотаций, с которыми компилятор уже знаком, таким как несколько встроенных аннотаций в JDK и ваша пользовательская аннотация, компилятор не знает функцию вашей аннотации и, конечно же, не знает как с этим бороться., часто просто выбирают, компилировать ли в файл байт-кода в соответствии с областью действия аннотации, и все.
метааннотация
«Мета-аннотации» — это аннотации, используемые для изменения аннотаций, обычно используемые в определении аннотаций, например:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Это определение нашей аннотации @Override. Вы можете видеть, что аннотации @Target и @Retention — это то, что мы называем «мета-аннотациями». «Мета-аннотации» обычно используются для указания такой информации, как жизненный цикл аннотации и цель действия. . . .
В JAVA есть следующие «мета-аннотации»:
- @Target: цель аннотации
- @Retention: жизненный цикл аннотации
- @Documented: следует ли включать аннотацию в документацию JavaDoc.
- @Inherited: разрешить ли подклассам наследовать эту аннотацию.
Среди них @Target используется, чтобы указать, на кого в конечном итоге может воздействовать измененная аннотация, то есть указать, что ваша аннотация используется для изменения метода? Изменено? Он также используется для изменения атрибутов поля.
@Target определяется следующим образом:
Мы можем передать значение для этого значения следующими способами:
@Target(value = {ElementType.FIELD})
Аннотации, измененные этой аннотацией @Target, будут работать только с полями-членами, а не с методами или классами. где ElementType — тип перечисления со следующими значениями:
- ElementType.TYPE: позволяет декорированным аннотациям воздействовать на классы, интерфейсы и перечисления.
- ElementType.FIELD: позволяет работать с полями атрибутов
- ElementType.METHOD: позволяет воздействовать на методы
- ElementType.PARAMETER: позволяет воздействовать на параметры метода.
- ElementType.CONSTRUCTOR: позволяет воздействовать на конструкторы
- ElementType.LOCAL_VARIABLE: позволяет воздействовать на локальные локальные переменные
- ElementType.ANNOTATION_TYPE: разрешено работать с аннотациями
- ElementType.PACKAGE: разрешено работать с пакетами
@Retention используется для указания жизненного цикла текущей аннотации, его основное определение выглядит следующим образом:
Аналогично, у него также есть свойство value:
@Retention(value = RetentionPolicy.RUNTIME
Здесь RetentionPolicy по-прежнему является типом перечисления и имеет следующие значения перечисления:
- RetentionPolicy.SOURCE: текущая аннотация видна во время компиляции и не будет записана в файл класса.
- RetentionPolicy.CLASS: отбрасывается на этапе загрузки класса и будет записан в файл класса.
- RetentionPolicy.RUNTIME: постоянно сохраняется, может быть получен путем отражения.
Аннотация @Retention указывает жизненный цикл измененной аннотации, одна видна только во время компиляции и будет отброшена после компиляции, а другая будет скомпилирована компилятором в файл класса, будь то класс или метод. , или даже поле, все они имеют таблицы атрибутов, а виртуальная машина JAVA также определяет несколько таблиц атрибутов аннотаций для хранения аннотационной информации, но эту видимость нельзя вывести в область методов и она будет отброшена при загрузке класса. один из них — постоянная видимость.
Остальные два типа аннотаций мало используются в нашей повседневной жизни и относительно просты, мы не будем их подробно здесь представлять, нужно только знать их соответствующие функции. Аннотация, измененная аннотацией @Documented, будет сохранена в документе документа, когда мы выполним упаковку документа JavaDoc, в противном случае она будет удалена во время упаковки. Аннотация, измененная аннотацией @Inherited, является наследуемой, что означает, что наша аннотация изменяет класс, и подкласс класса автоматически наследует аннотацию родительского класса.
Три встроенных аннотации в JAVA
В дополнение к вышеупомянутым четырем мета-аннотациям JDK также предопределил для нас три другие аннотации, а именно:
- @Override
- @Deprecated
- @SuppressWarnings
Всем должна быть знакома аннотация @Override, ее определение выглядит следующим образом:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
У него нет никаких свойств, поэтому он не может хранить никакую другую информацию. Его можно использовать только в методах, и он будет удален после компиляции.
Итак, вы видите, что это типичная «помеченная аннотация», которая известна только компилятору.В процессе компиляции java-файла в байт-код компилятор обнаруживает, что метод был украшен аннотацией. в родительском классе есть функция с такой же сигнатурой метода, иначе она, естественно, не скомпилируется.
Основное определение @Deprecated выглядит следующим образом:
Это по-прежнему «помеченная аннотация», которая существует постоянно и может изменять все типы.Функция состоит в том, чтобы пометить текущий класс или метод или поле и т. д., который больше не рекомендуется для использования и может быть удален в следующем JDK. версия.
Конечно, компилятор не заставляет вас что-либо делать, он просто сообщает вам, что JDK больше не рекомендует использовать текущий метод или класс, и рекомендует использовать замену.
@SuppressWarnings в основном используется для подавления предупреждений Java, его основное определение выглядит следующим образом:
У него есть атрибут value, который требует от вас активной передачи значения. Что представляет собой это значение? Это значение представляет тип предупреждения, которое необходимо подавить. Например:
public static void main(String[] args) {
Date date = new Date(2018, 7, 11);
}
Для такого фрагмента кода компилятор выдаст предупреждение при запуске программы.
Предупреждение: (8, 21) java: Дата (int, int, int) в java.util.Date устарела
И если мы не хотим, чтобы компилятор проверял наличие устаревших методов в коде при запуске программы, мы можем использовать аннотацию @SuppressWarnings и передать значение параметра в его атрибут value, чтобы подавить проверку компилятора.
@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
Date date = new Date(2018, 7, 11);
}
Таким образом, вы обнаружите, что компилятор больше не проверяет наличие устаревших вызовов методов в основном методе, что подавляет проверку компилятором таких предупреждений.
Конечно, в JAVA есть много типов предупреждений, и все они соответствуют строке.Установив значение атрибута value, можно подавить проверку этого типа предупреждений.
Соответствующее содержание пользовательских аннотаций не будет повторяться. Это относительно просто. Вы можете настроить аннотацию с помощью синтаксиса, подобного следующему.
public @interface InnotationName{
}
Конечно, при настройке аннотаций вы можете дополнительно использовать мета-аннотации для оформления, чтобы можно было более конкретно указать жизненный цикл, область действия и другую информацию ваших аннотаций.
Аннотации и размышления
В приведенном выше содержании мы представили детали использования аннотаций, а также вкратце упомянули, что «суть аннотаций — это интерфейс, который наследует интерфейс Annotation». .
Во-первых, мы настраиваем тип аннотации:
Здесь мы указываем, что аннотация Hello может изменять только поля и методы, и аннотация живет вечно, чтобы мы могли получить ее через отражение.
Как мы уже говорили, спецификация виртуальной машины определяет ряд таблиц атрибутов, связанных с аннотациями, то есть поля, методы или классы сами по себе, если они изменены аннотациями, они могут быть записаны в файлы байт-кода. Существуют следующие типы атрибутивных таблиц:
- RuntimeVisibleAnnotations: аннотации, видимые во время выполнения.
- RuntimeInVisibleAnnotations: аннотации, невидимые во время выполнения.
- RuntimeVisibleParameterAnnotations: аннотации параметров метода, видимые во время выполнения.
- RuntimeInVisibleParameterAnnotations: аннотации параметров метода, которые не отображаются во время выполнения.
- AnnotationDefault: значение по умолчанию для элемента класса аннотаций.
Цель демонстрации таблиц атрибутов, связанных с этими аннотациями виртуальной машины, состоит в том, чтобы дать вам общее представление о том, как аннотации хранятся в файле байт-кода.
Поэтому для класса или интерфейса в классе Class предусмотрены следующие методы для аннотаций отражения.
- getAnnotation: возвращает указанную аннотацию
- isAnnotationPresent: определяет, изменен ли текущий элемент указанной аннотацией.
- getAnnotations: возвращает все аннотации
- getDeclaredAnnotation: возвращает указанную аннотацию этого элемента.
- getDeclaredAnnotations: возвращает все аннотации этого элемента, за исключением унаследованных от родительского класса.
Методы связанных аннотаций отражения в методах и полях в основном схожи, поэтому я не буду повторять их здесь, давайте рассмотрим полный пример ниже.
Во-первых, установите параметр запуска виртуальной машины, который фиксирует динамический прокси-класс JDK.
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
Затем основная функция.
Мы сказали, что аннотации по сути являются интерфейсом, который наследует интерфейс Annotation, и когда вы получаете экземпляр класса аннотаций через рефлексию, то есть метод getAnnotation здесь, по сути, JDK генерирует аннотацию, реализующую нашу аннотацию через динамический прокси механизм (интерфейс) прокси-класса.
После того, как мы запустим программу, мы увидим в выходной директории вот такой прокси-класс, который после декомпиляции выглядит так:
Прокси-класс реализует интерфейс Hello и переопределяет все его методы, включая метод значения и методы, которые интерфейс Hello наследует от интерфейса Annotation.
И кто этот ключевой экземпляр InvocationHandler?
AnnotationInvocationHandler — обработчик, специально используемый для обработки аннотаций в JAVA Дизайн этого класса также очень интересен.
Существует memberValues, представляющий собой пару ключ-значение карты, ключ — это имя нашего атрибута аннотации, а значение — это значение, которое изначально было присвоено атрибуту.
И этот метод invoke очень интересен.Обратите внимание, что наш прокси-класс проксирует все методы в интерфейсе Hello, поэтому сюда будут передаваться вызовы любого метода в прокси-классе.
var2 указывает на экземпляр вызываемого метода, и здесь сначала переменная var4 используется для получения краткого имени метода, а затем структура switch определяет, кто является текущим вызывающим методом.Если это один из четырех методов в аннотации, присвоить var7 определенное значение.
Если текущим вызываемым методом является toString, equals, hashCode, annotationType, реализация этих методов была предварительно определена в экземпляре AnnotationInvocationHandler, и его можно вызывать напрямую.
Затем, если var7 не соответствует этим четырем методам, это означает, что текущий метод вызывает метод, объявленный пользовательским байтом аннотации, например, метод значения нашей аннотации Hello.В этом случае значение, соответствующее этому свойству аннотации, будет извлечено из нашей карты аннотаций.
На самом деле, дизайн аннотаций в JAVA лично мне кажется немного античеловеческим, это явно работа атрибутов, и она должна быть реализована методами. Конечно, если у вас есть разные мнения, пожалуйста, оставьте сообщение для обсуждения.
Наконец, давайте подытожим принцип работы всей аннотации отражения:
Во-первых, мы можем присвоить значения атрибутам аннотации в виде пар ключ-значение, например: @Hello(value = "hello").
Затем вы украшаете элемент аннотациями, компилятор просматривает аннотации для каждого класса или метода во время компиляции, выполняет базовую проверку, разрешено ли использование ваших аннотаций в текущей позиции, и, наконец, записывает информацию об аннотациях. в таблицу атрибутов элемента.
Затем, когда вы выполняете отражение, виртуальная машина вынимает все аннотации, чей жизненный цикл находится в RUNTIME, и помещает их в карту, создает экземпляр AnnotationInvocationHandler и передает ей карту.
Наконец, виртуальная машина будет использовать механизм динамического прокси JDK для создания прокси-класса целевой аннотации и инициализации процессора.
Таким образом создается экземпляр аннотации, который по сути является прокси-классом.Вы должны понимать логику реализации метода вызова в AnnotationInvocationHandler, который является ядром. В одном предложении это,Возвращает значение атрибута аннотации по имени метода.
Весь код, изображения, файлы в статье хранятся в облаке на моем GitHub:
(https://github.com/SingleYam/overview_java)
Добро пожаловать в официальную учетную запись WeChat: OneJavaCoder, все статьи будут синхронизированы в официальной учетной записи.