Retention policy java
Аннотации в JAVA: обзор синтаксиса и создание собственных
Статья ориентирована больше на новичков, или тех, кто еще не работал с данным механизмом в языке. Я постараюсь рассказать, что это такое, зачем они нужны, и как можно самому создавать удобные для себя аннотации.
Аннотации представляют из себя дескрипторы, включаемые в текст программы, и используются для хранения метаданных программного кода, необходимых на разных этапах жизненного цикла программы.
Информация, хранимая в аннотациях, может использоваться соответствующими обработчиками для создания необходимых вспомогательных файлов или для маркировки классов, полей и т.д.
Синтаксис
Аннотация задается описанием соответствующего интерфейса.
Например так:
Как видно из примера выше, аннотация определяется описанием с ключевым словом interface и может включать в себя несколько полей, которые можно задать как обязательными, так и не обязательными. В последнем случае подставляется default значение поля.
Также из примера видно, что саму аннотацию можно пометить несколькими аннотациями.
Разберемся для начала, чем можно пометить собственную аннотацию, и зачем.
Аннотация @Retention позволяет указать жизненный цикл аннотации: будет она присутствовать только в исходном коде, в скомпилированном файле, или она будет также видна и в процессе выполнения. Выбор нужного типа зависит от того, как вы хотите использовать аннотацию, например, генерировать что-то побочное из исходных кодов, или в процессе выполнения стучаться к классу через reflection.
Аннотация @Target указывает, что именно мы можем пометить этой аннотацией, это может быть поле, метод, тип и т.д.
Аннотация @Documentedуказывает, что помеченная таким образом аннотация должна быть добавлена в javadoc поля/метода и т.д.
Например, класс, помеченный аннотацией без @Documented, будет выглядеть так:
А если в описание аннотации добавить @Documented, получим:
Аннотация @Inherited помечает аннотацию, которая будет унаследована потомком класса, отмеченного такой аннотацией.
Сделаем для примера пару аннотаций и пометим ими класс.
Класс ChildClass унаследует от родительского класса только аннотацию PublicAnnotate.
Пример своей аннотации.
Попробуем теперь написать рабочий пример с использованием аннотаций.
Представим себе, что у нас есть какой-то самодельный проект, который на вход получает класс, специально заанотированный, чтобы проект мог управлять жизненным циклом объектов этого класса, и пусть там будут аннотации StartObject, StopObject для описания методов класса, и ControlledObject для описания самого класса. Последней аннотации дадим еще поле name, путь там хранится якобы имя для поиска.
Аннотации будут выглядеть так:
Напишем модуль, проверяющий подходит ли класс для загрузки в наш гипотетический проект или нет.
Сперва определим сам проверяемый класс.
Для того, чтобы работать с классом, сначала необходимо загрузить класс в контекст приложения. Используем:
);
Далее, через механизм reflection мы получаем доступ к полям и аннотациям класса.
Проверим наличие аннотированных методов в классе и аннотации на самом классе:
Запустив, на выходе мы получим:
Start annotaton — true; Stop annotation — true.
Если попробовать убрать одну из аннотаций, то вывод сообщит о несоответствии требованиям.
Надеюсь, мне удалось показать, что аннотации предоставляют широкие возможности для работы с кодом программ, и имеет смысл не только пользоваться стандартными аннотациями языка, но и облегчать себе жизнь, создавая аннотации под свои нужды.
Подробнее ознакомиться с данным механизмом могу порекомендовать в книге «Java 2. Библиотека профессионала, том 2. Тонкости программирования» Кей С. Хорстманна, Гари Корнелла, или на сайте oracle, где так же есть подробные туториалы.
Как написать свою Аннотацию в Java?
Довольно таки часто можно заметить @Annotation в проектах Java, но не все и не всегда задумываются, что это и как ними работать, в этом уроке я более подробно объясню, что такое аннотации и как с ними работать к тому же напишем свою аннотацию.
Шаг 1. Теория
Аннотация(@Annotation) — специальная форма метаданных, которая может быть добавлена в исходный код.
Аннотации используются для анализа кода, компиляции или выполнения. Аннотированы могут быть пакеты, классы, методы, переменные и параметры.
Например всем известная библиотека JUnit использует аннотации для проведения модульного тестирования:
где @Test и @Ignore – аннотации.
Аннотация выполняет следующие функции:
1) дает необходимую информацию для компилятора;
2) дает информацию различным инструментам для генерации другого кода, конфигураций и т. д.;
3) может использоваться во время работы кода;
Самая часто встречаемая аннотация, которую встречал любой программист, даже начинающий это @Override:
Шаг 2. Как создать свою Аннотацию.
В интернете большое количество документации по поводу аннотаций, потому что аннотации очень сильно облегчают жизнь программисту.
Написать свою аннотацию не так сложно, как могло бы казаться.
Все что нам нужно для создание своей аннотации, это создать файл About.java назвать его можно как угодно, но от этого названия будет зависеть имя вашей аннотации, если мы назвали его About.java то аннотация будет выглядеть так @About.
Напишем в созданном файле About.java следующий код:
как вы видите на месте где обычно пишут class или interface у нас написано @interface.
По просту структура практически та же, что и у интерфейсов, только пишется @interface.
@interface – указывает на то, что это аннотация
default – говорит про то, что метод по умолчанию будет возвращать определённое значение.
Вот и все аннотация готова теперь ею можно пользоваться, но есть одно НО, аннотацию можно сконфигурировать.
Шаг 3. Конфигурации для аннотации.
Так как мы в Шаге 2 ничего не конфигурировали, то она может применяться к чему только угодно, к классам, методам, атрибутам и т. п.
Для того чтобы ограничить использование аннотации её нужно проаннотировать 🙂
Для этого существует аннотация @Target.
@Target(ElementType.PACKAGE) – только для пакетов;
@Target(ElementType.TYPE) – только для классов;
@Target(ElementType.CONSTRUCTOR) – только для конструкторов;
@Target(ElementType.METHOD) – только для методов;
@Target(ElementType.FIELD) – только для атрибутов(переменных) класса;
@Target(ElementType.PARAMATER) – только для параметров метода;
@Target(ElementType.LOCAL_VARIABLE) – только для локальных переменных.
В случае если вы хотите, что бы ваша аннотация использовалась больше чем для одного типа параметров, то можно указать @Target следующим образом:
тут мы говорим, аннотацию можно использовать только для параметров метода и для локальных переменных.
Шаг 4. Реализация
Вы наверное уже заметили что написанная нами аннотация About – это практически интерфейс который ничего не реализовывает, по этому нам нужно указать что же должно происходить с аннотированным фрагментом кода.
Как вам уже известно Аннотация это всего лишь маркер который выделяет что-то, и по этому маркеру мы можем легко найти фрагмент кода и что-то сделать.
Давайте реализуем всем известный framework JUnit.
как вы уже заметили у наc появилась новая конфигурация для нашей аннотации @Retention
@Retention – эта аннотация позволит позволит сохранять нашу аннотацию JVM во время выполнения, что даст возможность использовать отображение(reflection).
Теперь мы должны для нашей аннотации написать класс анализатор. Этот класс будет анализировать наши аннотации и выполнять некоторые действия, связанные с аннотируемыми параметрами.
Имейте в виду, что если у вас есть более чем одна пользовательская аннотации, то целесообразно иметь отдельный анализатор для каждой аннотации что вы определите.
Что должен делать анализатор? Он использует Java отражения для доступа к аннотируемым данным.
Пример анализатора для @Test:
Обратите внимание что в строке 10 мы вызываем аннотируемый метод.
Это все. Анализатор готов к использованию, но постойте, мы не реализовали атрибуты аннотации. Эта часть немного сложнее. Потому что вы не можете напрямую обращаться к этим атрибутам.
В строке 10 мы получаем доступ к атрибуту аннотации и дальше в строке 11 получаем значение атрибута аннотации, в нашем случае это значение типа Class так как expected – это ожидаемая ошибка и мы будем получать exception.
Ну и в строке 16 мы проверяем ожидаемый Exception с полученным.
Шаг 5. Использование
Давайте теперь используем нашу аннотацию:
Внимание!
В уроке я описал теоретическое объяснение Аннотациям и кратко продемонстрировал реализацию, скачав исходник полной реализации вы можете выше нажав кнопку Скачать.
Аннотации в Java и их обработка в RUNTIME с помощью рефлексии
Аннотации – это своего рода маркеры, которые служат знаком либо компилятору, либо обработчику скомпилированных классов, либо самому приложению во время его выполнения – знаком что-то сделать. Для аннотаций всегда нужен обработчик, сами они ничего не делают.
Пример обработчика – тестовый фреймворк JUnit. Для него мы помечаем метод аннотацией @Test, и он обрабатывает этот метод как тестовый. Вне фреймворка аннотация @Test ничего не значит.
Другой пример – ORM-фреймворк Hibernate. Еще есть Spring Framework, проект Lombook.
Задача
В этой статье мы сконцентрируемся на обработке аннотаций в Runtime. Мы напишем свой собственный обработчик – упрощенный фреймворк для тестирования, который запускает тесты. Обработчик будет искать методы, аннотированные аннотацией @Test, и запускать их.
Наша задача создать статический метод, которому передается объект типа Class, содержащий тестируемые методы:
Метод должен запустить все аннотированные с помощью @Test методы класса testClass и посчитать, сколько тестов прошло, а сколько нет.
Для начала мы создадим аннотацию @Test (свою, а не ту, что есть в JUnit). Хотя теоретически можно было бы переиспользовать и ту, просто написать для нее свой обработчик.
Создание аннотации @Test
Синтаксис аннотаций довольно прост, в нашем случае определение аннотации @Test выглядит так:
@Target и @Retention – это метаанотации, то есть аннотации, которые применяются к аннотации.
Значение ElementType.METHOD в @Target показывает, что наша аннотация @Test применима к методам, то есть с помощью нее можно аннотировать только метод:
А вообще аннотации можно применять практически к любому элементу кода – классу, параметрам, полям…Просто в нашем случае требуется аннотировать только методы, что мы и указываем в определении аннотации. Если теперь в коде применить аннотацию @Test к полю, классу или другому элементу, то компилятор выдаст ошибку.
Виды RetentionPolicy
Сделаем небольшое отступление о метааннотации @Retention(RetentionPolicy.RUNTIME). Она, как и @Target, доступна в JDK по умолчанию и служит для оформления пользовательских аннотаций.
Вообще есть три уровня RetentionPolicy:
- RetentionPolicy.SOURCE
- RetentionPolicy.CLASS (действует по умолчанию, если метааннотация @Retention не указана)
- RetentionPolicy.RUNTIME
“Retention” переводится как “сохранение”, “удержание”, имеется в виду где (на каком этапе) сохраняются/выживают аннотации.
Уровни вы списке выше упорядочены по степени выживания аннотаций (1 – самая короткоживущая).
- Аннотации с RetentionPolicy.SOURCEостаются только в исходниках, в скомпилированных файлах их уже нет. Они интересны либо статическим анализаторам кода, либо обработчику аннотаций на этапе компиляции. Как создать дополнительный обработчик исходного кода подробно рассматривается в статье – в ней мы генерируем новый класс на этапе компиляции. Так работает Lombok – он на основе аннотаций генерирует сеттеры и геттеры в исходниках.
Аннотации с RetentionPolicy.CLASS остаются в скомпилированных файлах, но на этапе выполнения программы в машинном коде их уже нет. Эти аннотации могут быть интересны обфускатору кода (который переименовывает и сокращает скомпилированный код). Или, например, служебной библиотеке Javassist. Как изменить байт-код с помощью этой библиотеки есть в другой статье.
В этой статье мы рассмотрим, как работать с аннотациями на этапе выполнения кода в (runtime), для этого аннотации должны иметь самый сильный уровень RetentionPolicy.RUNTIME. Мы будем использовать Java Reflection – библиотеку, которая может считать и изменить информацию о классе в процессе выполнения программы. А также запустить методы класса.
Класс Class
Теперь пара слов о параметре метода TestRunner.run(Class testClass).
Тестировать мы будем класс Sample1, а конкретно, его аннотированные с помощью @Test методы:
Значит в параметр мы будем передавать объект Class, полученный из класса Sample1:
Библиотека Java Reflection имеет дело с объектом типа Class. Она может получить из этого объекта список методов, полей, конструкторов, аннотаций для данного класса.
Объект типа Class создается для каждого класса приложения при его загрузке. То есть при первом обращении к классу, например при создании объекта этого класса, загружается скомпилированный .class файл, содержащий байт-код класса, Sample1.class. Его содержимое загружается в память VM и на его основе в heap создается объект типа Class , в котором есть вся информация о классе – какие в нем методы, поля, конструкторы, аннотации.
Давайте приступим к реализации логики – получим из этого объекта список тестовых методов и запустим их.
Метод TestRunner.run()
Чтобы запустить методы, надо получить их список. В цикле мы идем по полученным методам класса и выбираем аннотированные:
Далее, в InvocationTargetException оборачивается исключение, выброшенное изнутри тестового метода. То есть попадание в этот блок catch() считается провалом теста.
В блок IllegalAccessException мы попадаем, если некорректно вызвали invoke(): например, передали ему null, когда m не статический. Эта ситуация считается невалидным тестом.
Если же ни в какой блок catch() мы не попали, что тест считается валидным и пройденным.
В принципе все, таким образом подсчитывается и выводится количество пройденных и не пройденных тестов.
В реальном фреймворке типа JUnit все гораздо сложнее, потому что поддерживается много аннотаций – @Before, @After и т.д.
А мы давайте еще сделаем поддержку аннотации @ExceptionTest – она используется, чтобы показать, что тест должен выбрасывать определенное исключение.
Тестирование методов на выбрасывание исключения
Напишем отдельный класс с методами, которые должны выбрасывать исключение, и нам надо протестировать, что они их действительно выбрасывают.
Как маркер выбрасывания исключения у нас создана аннотация @ExceptionTest:
Внутри аннотации указан тип исключения, который должен выбрасывать метод.
Обратите внимание, что метод doublyBad() аннотирован аннотацией @ExceptionTest дважды. Это означает, что надо протестировать, что метод выбрасывает любое из этих исключений.
Здесь мы подошли к понятию repeatable annotation.
Repeatable аннотации
Аннотации, которыми можно аннотировать элемент несколько раз, называются repeatable. @ExceptionTest является repeatable-аннотацией. Синтаксис их не прост, а особенно обработка.
Чтобы определить аннотацию как repeatable, мы должны аннотировать ее дополнительной метааннотацией @Repeatable(ExceptionTestContainer.class):
Здесь внутри @Repeatable задана вторая аннотация ExceptionTestContainer.class, и ее нам тоже надо определить. Она служит контейнером для повторяемых значений:
В ExceptionTestContainer должен быть массив типа ExceptionTest. Вот так они закручены, и с обработкой дела обстоят не лучше.
Обрабатывать повторяемые аннотации надо осторожно.
Итак, допишем наш цикл так, чтобы тесты, аннотированные с помощью @ExceptionTest, тоже запускались и проверялись.
Поддержим @ExceptionTest
Для этого допишем еще один блок if внутри цикла в TestRunner.run():
В соответствии с нашим замечанием выше об особенностях работы метода isAnnotationPresent() с повторяемыми аннотациями, мы проверяем методы на наличие любой из двух аннотаций: ExceptionTest и ExceptionTestContainer.
Далее ситуация противоположная – чтобы тест был пройден, исключение должно быть выброшено, то есть мы должны попасть в блок catch(). Причем исключение должно быть именно одного из тех типов, перечисленных в аннотации. Что мы и проверяем.
Аннотация методов, annotation
Аннотация «annotation» в языке Java – это специальная форма синтетических метаданных, которая может быть добавлена в исходный код. Аннотации используются для анализа кода, компиляции или выполнения. Аннотированы могут быть пакеты, классы, методы, переменные и параметры.
Аннотация выполняет следующие функции :
- предоставляет необходимую информацию для компилятора;
- предоставляет информацию различным инструментам для генерации другого кода, конфигураций и т. д.;
- может быть использована во время работы кода.
Встроенные аннотации : @Override, @Deprecated, @SuppressWarnings
Встроенные аннотации отслеживаются средой разработки IDE и применяются к java-коду метода :
@Override | Проверка переопределения метода. IDE вызывает предупреждение компиляции, если метод не найден в родительском классе. |
@Deprecated | IDE отмечает, что метод устарел и вызывает предупреждение компиляции, если метод используется. |
@SuppressWarnings | Аннотация указывает IDE подавить предупреждения компиляции. |
Аннотации, применяемые к другим аннотациям : @Retention, @Documented, @Target, @Inherited
@Retention | Определяет, как отмеченная аннотация будет храниться — в коде, в скомпилированном классе или во время работы кода. |
@Documented | Отмечает аннотацию для включения в документацию. |
@Target | Отмечает аннотацию как ограничивающую, какие элементы java-аннотации могут быть к ней применены. |
@Inherited | Отмечает, что аннотация может быть расширенна подклассами аннотируемого класса. |
Первоначально в платформе Java имелся механизм, предваряющий механизм аннотаций — например, модификатор transient или тэг @deprecated. В сентябре 2002 года сообществу Java представлен документ JSR-175, описывающий основные тезисы по аннотациям. Он был утвержден в 2004 году. Аннотации стали доступны в самом языке начиная с версии Java 5.0 и описаны в JSR-269. В версии Java 6 аннотации были интегрированы в компилятор javac.
Пример аннотации :
Синтаксис аннотации, @interface
Аннотации представляют из себя дескрипторы, включаемые в текст программы, и используются для хранения метаданных программного кода, необходимых на разных этапах жизненного цикла программы. Информация, хранимая в аннотациях, может использоваться соответствующими обработчиками для создания необходимых вспомогательных файлов или для маркировки классов, полей и т.д. То есть, аннотации могут быть применены к декларациям классов, полей, методов, ну и конечно же аннотаций.
Для описания новой аннотации используется ключевое слово @interface. Например :
Пример использования аннотации Description :
Пример аннотации с параметрами:
В данном примере аннотация включает в себя несколько полей (name, type), которые можно задать как обязательными, так и необязательными. В последнем случае подставляется default значение поля.
Из синтаксиса аннотации следует, что саму аннотацию можно пометить несколькими параметрами. В качестве типов параметров аннотации могут использоваться только примитивные типы, перечисления и класс String. Если у аннотации нет элементов, ее называют маркером (marker annotation type). В этом случае при использовании аннотации круглые скобки можно не писать.
Параметры аннотации
@Retention
Аннотация @Retention позволяет определить жизненный цикл аннотации : будет она присутствовать только в исходном коде, в скомпилированном файле, или она будет также видна и в процессе выполнения. Выбор нужного типа аннотации @Retention зависит от того, как будет использоваться данная аннотацию. Например, генерировать что-то побочное из исходных кодов, или в процессе выполнения «стучаться» к классу через reflection.
RetentionPolicy.SOURCE | аннотация используется на этапе компиляции и должна отбрасываться компилятором |
RetentionPolicy.CLASS | аннтоация будет записана в class-файл компилятором, но не должна быть доступна во время выполнения (runtime) |
RetentionPolicy.RUNTIME | аннотация будет записана в class-файл и доступна во время выполнения через reflection |
@Target
Параметр @Target указывает, что именно должно быть помечено аннотацией. Это может быть поле, метод, тип и т.д. Для этого следует использовать параметры к аннотации.
@Target(ElementType.PACKAGE) | только для пакетов |
@Target(ElementType.TYPE) | только для классов |
@Target(ElementType.CONSTRUCTOR) | только для конструкторов |
@Target(ElementType.METHOD) | только для методов |
@Target(ElementType.FIELD) | только для атрибутов(переменных) класса |
@Target(ElementType.PARAMATER) | только для параметров метода |
@Target(ElementType.LOCAL_VARIABLE) | только для локальных переменных |
В случае, если необходимо, что бы аннотация использовалась больше чем для одного типа параметров, то можно указать @Target следующим образом:
В данном случае аннотацию можно использовать только для параметров метода и для локальных переменных.
Параметр @Documented указывает, что помеченные таким образом аннотацией класс/метод/поле должны быть добавлены в javadoc. Например, класс, помеченный аннотацией без @Documented, будет выглядеть так:
А если в описание аннотации добавить @Documented, получим:
Использование аннотации
Предположим, нам нужно ограничить доступ к некоторым функциям веб-приложения для разных пользователей. Иными словами необходимо реализовать права (permissions). Для этого можно добавить следующее перечисление в класс пользователя:
Создадим аннотацию, которую будем использовать для проверки прав доступа :
Предположим, что у нас есть некоторое действие, право на выполнение которого мы хотим ограничить, например, UserDeleteAction. Для этого добавляем аннотацию на это действие следующим образом:
Теперь используя reflection, можно принимать решение, разрешать или не разрешать выполнение определенного действия :
Пример анализатора аннотации
Создадим класс анализатора, который будет определять аннотации и выполнять некоторые действия, связанные с аннотируемыми параметрами. Необходимо иметь в виду, что если используется более чем одна пользовательская аннотации, то целесообразно иметь отдельный анализатор для каждой аннотации.
Что должен делать анализатор? Он использует reflection для доступа к аннотируемым данным. Пример анализатора для класса @Test:
Сочетание использования аннотации и reflection позволяет выполнить определенную проверку и вызвать метод на исполнение через invoke. Анализатор готов к использованию. Для использования атрибутов аннотации расширим код.
После получения доступа к атрибуту аннотации определяем ее значение. В нашем случае это значение типа Class, так как expected — это ожидаемая ошибка и мы будем получать exception.
Пример использования класса анализа аннотации: