Аннотации java что это
Аннотации в Java, часть I
Это первая часть статьи, посвященной такому языковому механизму Java 5+ как аннотации. Она имеет вводный характер и рассчитана на Junior разработчиков или тех, кто только приступает к изучению языка.
Я занимаюсь онлайн обучением Java и опубликую часть учебных материалов в рамках переработки курса Java Core.
Также я веду курс «Scala for Java Developers» на платформе для онлайн-образования udemy.com (аналог Coursera/EdX).
Поехали!
Учебный пример: снабдить классы пользователя мета-информацией о «версии класса».
Итерация #1:
Просто ставим @ перед interface.
Итерация #2:
У аннотаций могут быть атрибуты.
Заполнятся при использовании
Аннотация выше полностью эквивалентна следующей (без public). В этом аннотации схожи с интерфейсам: отсутствие модификатора области видимости автоматически означает public (а не package private как у классов).
С protected и private — не компилируется
Далее я буду использовать вариант без модификатора public
Итерация #3:
Если объявить атрибут с именем value, то его можно опускать при использовании
Хотя можно и по старинке
Итерация #4:
Для атрибута можно объявить значения по умолчанию
Теперь у нас два варианта использования. Так
Но не вот так (слушай, обидно, да)
Итерация #5:
Атрибуты могут иметь тип массива
Но только одномерного
Итерация #6:
Возможен забавный трюк: аннотация — атрибут аннотации
Применяется вот так
У аннотаций много ограничений. Перечислим некоторые из них.
Ограничение: тип атрибута
Ну что же, давайте действовать в рамках ограничений
Итерация #7:
В качестве типа атрибута нельзя использовать «обычные» классы Java (за исключением java.lang.String и java.lang.Class), скажем java.util.Date
Но можно эмулировать записи/структуры на аннотациях
Итерация #8:
Атрибутом аннотации может быть enum. Из приятного, его можно объявить в объявлении аннотации (как и в объявлении интерфейса тут может быть объявление enum, class, interface, annotation)
Итерация #9:
Атрибутом аннотации может быть классовый литерал.
Аннотация версии включает ссылку на предыдущую версию класса.
Первая версия класса
Вторая версия со ссылкой на первую
// Да, я знаю, что нормальные люди не включают версию класса в имя класса. Но знаете как нудно придумывать примеры согласованные с реальной практикой?
Итерация #10:
Менее тривиальный пример с классовым литералом, где я не удержался и добавил generic-ов.
Интерфейс «сериализатора» — того, кто может записать экземпляр T в байтовый поток вывода
Конкретный «сериализатор» для класса MyClass
Аннотация, при помощи которой мы «приклеиваем сериализатор» к конкретному классу
Ну и сам класс MyClass отмеченный, как сериализуемый «своим сериализатором» MySerializer
Итерация #11:
Сложный пример
Ну и наконец использование аннотации
Ограничение: значения атрибутов — константы времени компиляции/загрузки JVM
Должна быть возможность вычислить значения атрибутов аннотаций в момент компиляции или загрузки класса в JVM.
А вот вызовы методов — это уже runtime, это уже запрещено
Заключение
Контакты
Я занимаюсь онлайн обучением Java (вот курсы программирования) и публикую часть учебных материалов в рамках переработки курса Java Core. Видеозаписи лекций в аудитории Вы можете увидеть на youtube-канале, возможно, видео канала лучше систематизировано в этой статье.
Практическое применение аннотации в Java на примере создания Telegram-бота
Рефлексия в Java — это специальное API из стандартной библиотеки, которая позволяет получить доступ к информации о программе во время выполнения.
Большинство программ так или иначе пользуются рефлексией в различных его видах, ведь его возможности трудно уместить в одной статье.
Многие ответы заканчиваются на этом, но что более важно, это понимание вообще концепции рефлексии. Мы гонимся за короткими ответами на вопросы, чтобы успешно пройти собеседование, но не понимаем основы — откуда это взялось и что именно понимать под рефлексией.
В этой статье мы коснемся всех этих вопросов применительно к аннотациям и на живом примере увидим как использовать, находить и писать свою.
Рефлексия
Я считаю, что ошибочно будет думать, что рефлексия в Java ограничивается лишь каким-то пакетом в стандартной библиотеке. Поэтому предлагаю рассмотреть его как термин, не привязывая конкретному пакету.
Reflection vs Introspection
Наряду с рефлексией также есть понятие интроспекции. Интроспекция — это способность программы получить данные о типе и других свойствах объекта. Например, это instanceof :
Это очень сильный метод, без чего Java не была бы такой, какая она есть. Тем не менее дальше получения данных он не уходит, и в дело вступает рефлексия.
Некоторые возможности рефлексии
Если говорить конкретнее, то рефлексия — это возможность программы исследовать себя во время выполнения и с помощью неё изменять своё поведение.
Поэтому пример, показанный выше, является не рефлексией, а лишь интроспекцией типа объекта. Но что же тогда является рефлексией? Например, создание класса или вызов метода, но весьма своеобразным способом. Ниже приведу пример.
Представим, что у нас нет никаких знаний о классе, который мы хотим создать, а лишь информация, где он находится. В таком случае мы не можем создать класс очевидным путём:
Воспользуемся рефлексией и создадим экземпляр класса:
Давайте также через рефлексию вызовем его метод:
От теории к практике:
Вопрос #1
Почему в invoke методе в примере сверху мы должны передавать экземпляр объекта?
Далее углубляться я не буду, так как мы уйдём далеко от темы. Вместо этого я оставлю ссылку на статью старшего коллеги Тагира Валеева.
Аннотации
Задумывались ли вы, как оно работает? Если не знаете, то, прежде чем читать дальше, попробуйте догадаться.
Типы аннотаций
Рассмотрим вышеприведённую аннотацию:
@Target — указывает к чему применима аннотация. В данном случае, к методу.
@Retention — длительность жизни аннотации в коде (не в секундах, разумеется).
@interface — является синтаксисом для создания аннотаций.
Если с первым и последним все более менее понятно (подробнее см. @Target в документации), то @Retention давайте разберем сейчас, так как он поможет разделить аннотации на несколько типов, что очень важно понимать.
Эта аннотация может принимать три значения:
Во втором случае аннотация будет доступна и во время выполнения, благодаря чему мы сможем её обработать, например получить все классы, которые имеют данную аннотацию.
В третьем случае аннотация будет удалена компилятором (её не будет в байт-коде). Обычно это бывают аннотации, которые полезны только для компилятора.
SuperCat
Попробуем добавить свою аннотацию (это здорово нам пригодится во время разработки).
Пусть у нас в доме будет два котика: Том и Алекс. Создадим аннотацию для суперкотика:
При этом Тома мы оставим обычным котом (мир несправедлив). Теперь попробуем получить классы, которые были аннотированы данным элементом. Было бы неплохо иметь такой метод у самого класса аннотации:
Но, к сожалению, такого пока метода нет. Тогда как нам найти эти классы?
ClassPath
Это параметр, который указывает на пользовательские классы.
Надеюсь, вы с ними знакомы, а если нет, то спешите изучить это, так как это одна из фундаментальных вещей.
Итак, узнав, где хранятся наши классы, мы сможем их загрузить через ClassLoader и проверить классы на наличие данной аннотации. Сразу приступим к коду:
Не советую использовать это в вашей программе. Код приведён только для ознакомительных целей!
Этот пример показателен, но используется только для учебных целей из-за этого:
Дальше мы узнаем, почему. А пока разберём по строчкам весь код сверху:
Чтобы разобраться, откуда мы берём эти файлы, рассмотрим JAR-архив, который создаётся, когда мы запускаем приложение:
Таким образом, classes — это только наши скомпилированные файлы в виде байт-кода. Тем не менее File — это ещё не загруженный файл, мы только знаем, где они находятся, но мы пока по-прежнему не можем видеть, что находится внутри них.
Поэтому загрузим каждый файл:
Всё, что сделано ранее, было только для того, чтобы вызвать этот метод Class.forName, который загрузит необходимый нам класс. Итак, финальная часть — это получение всех аннотаций, использованных на класс repoClass, а затем проверка, являются ли они аннотацией @SuperCat :
И готово! Теперь, когда у нас есть сам класс, то мы получаем доступ ко всем методам рефлексии.
Рефлексируем
Как и в примере сверху, мы можем просто создать новый экземпляр нашего класса. Но перед этим разберём несколько формальностей.
Итак, обработка обретает финальную форму:
И снова рубрика вопросов:
Подумайте пару минут, а затем сразу разберём ответы:
Ответ #3: Кошкам нужен дом, потому что они являются внутренними классами. Всё в рамках спецификации The Java Language Specification глава 15.9.3.
Подведём итоги и получим: Home.java
Сам он как раз-таки делает всё, что от него нужно. Тем не менее мы его используем неправильно.
Представьте себе, что вы работаете над проектов в котором 1000 и больше классов (всё-таки на Java пишем). И представьте, что вы будете загружать каждый класс, который найдёте в classPath. Сами понимаете, что память и остальные ресурсы JVM не резиновые.
Способы работы с аннотациями
Если бы не было другого способа работать с аннотациями, то использование их в качестве меток класса, как, например, в Spring, было бы весьма и весьма спорным.
Но всё же Spring вроде работает. Неужели из-за них моя программа такая медленная? К сожалению или к счастью, нет. Spring работает исправно (в этом плане), потому что использует несколько иной способ для работы с ними.
Прямо в байт-код
Все (надеюсь) так или иначе имеют представление, что такое байт-код. В нём хранится вся информация о наших классах и их метаданных (в том числе аннотаций).
Так почему бы нам её просто не прочитать (да, из байт-кода)? Но здесь я не буду реализовывать программу для её чтения из байт-кода, так как это заслуживает отдельной статьи. Впрочем, вы сами можете это сделать — это будет отличной практикой, которая закрепит материал статьи.
Для ознакомления с байт-кодом вы можете начать с моей статьи. Там я описываю базовые вещи байт-кода с программой Hello World! Статья будет полезна, даже если вы не собираетесь напрямую работать с байт-кодом. В нем описываются фундаментальные моменты, которые помогут ответить на вопрос: почему именно так?
После этого добро пожаловать на официальную спецификацию JVM. Если вы не хотите разбирать байт-код вручную (по байтам), то посмотрите в сторону таких библиотек, как ASM и Javassist.
Reflections
Reflections — библиотека с WTFPL лицензией, которая позволяет делать с ней всё, что вы захотите. Довольно быстрая библиотека для различной работы с classpath и метаданными. Полезным является то, что она может сохранять информацию о уже некоторых прочитанных данных, что позволяет сэкономить время. Можете покопаться внутри и найти класс Store.
spring-context
Я бы рекомендовал использовать библиотеку Reflections, так как внутри она работает через javassist, что свидетельствует о том, что используется чтение байт-кода, а не его загрузка.
Если ваши классы — это по сути managed beans, то есть находятся в контейнере Spring, то вам незачем повторно их сканировать. Вы просто можете получить доступ к этим бинам из самого контейнера.
Опять же, довольно редко вы должны будете использовать именно такой метод, но как вариант его стоит рассмотреть.
Я писал на нём бота для ВК. Вот репозиторий, с которым вы можете ознакомиться, но писал я его давно, а когда зашёл посмотреть, чтобы вставить ссылку в статью, то увидел, что через VK-Java-SDK я получаю сообщения с неинициализированными полями, хотя раньше всё работало.
Команды в нём представляют из себя вот что:
Примеры кода с аннотацией SuperCat вы можете найти в этом репозитории.
Практическое применение аннотаций в создании Телеграм-бота
Всё это было довольно длинным, но необходимым вступлением для работы с аннотациями. Далее, мы будем реализовывать бота, но цель статьи — это не мануал к его созданию. Это практическое применение аннотаций. Здесь могли быть что угодно: от консольных приложений до этих же самых ботов для вк, телеги и прочего.
Также здесь осознанно не будут выполнятся какие-то сложные проверки. Например, до этого в примерах не было никаких чеков на null или правильной обработки ошибок, не говоря уже об их логировании.
Все это делается для упрощения кода. Поэтому, если вы будете брать код из примеров, то не ленитесь доработать его, так вы лучше его поймете и настроите под свои нужды.
Мы будем использовать библиотеку TelegramBots с MIT лицензией для работы с API телеграма. Вы же можете использовать любую другую. Я выбрал её, потому что она могла работать как «c» (имеет версию со стартёром), так и «без» спринг-бута.
Собственно, я не хочу также усложнять код, добавляя какую-то абстракцию, если хотите, то можете сделать что-то универсальное, но подумайте, стоит ли оно того, поэтому для этой статьи мы часто будем использовать конкретные классы из этих библиотек, привязывая наш код к ним.
Reflections
Первый бот на очереди — это бот, написанный на библиотеке reflections, без Spring. Будем разбирать не всё подряд, а лишь основные моменты, в особенности нас интересует обработка аннотаций. До разбора в статье вы сами можете разобраться в его работе в моём репозитории.
Во всех примерах будем придерживаться того, что бот состоит из нескольких команд, причём эти команды мы не будем загружать вручную, а просто будем добавлять аннотации. Вот пример команды:
Также добавим логгеры. Их мы будем вызвать либо до обработки запроса, либо после, а также комбинировать их:
Но также мы можем добавить параметр, чтобы логгер срабатывал при определённых сообщениях:
Или срабатывал после обработки запроса:
Мы можем делать такое, так как executionTime принимает массив значений. Принцип работы прост, поэтому приступим к обработке этих аннотаций:
По сути, мы просто создаём мапу с именем команды, которую берём из значения value в аннотации. Исходный код здесь.
То же самое делаем с Log, только логгеров с одинаковыми паттернами может быть несколько, поэтому мы чуть меняем нашу структуру данных:
На каждый паттерн приходится несколько логгеров. Остальное всё так же.
Теперь в самом боте нам нужно настроить executionTime и перенаправлять запросы на эти классы:
Лучше всего узнать код самому и посмотреть в репозитории, а ещё лучше открыть его через IDE. Этот репозиторий подходит для начала работы и ознакомления, но в качестве бота он недостаточно хорош.
Спринговый бот
Это приобретает больше смысла при работе с экосистемой спринга:
Но я думаю, что, спринг — это хорошая среда не только для энтерпрайза/веб-приложений. Просто он содержит в себе очень много как официальных, так и пользовательских библиотек для своей экосистемы (под спрингом имеется в виду Spring Boot).
И, самое главное, он позволяет реализовать очень много паттернов различными способами, предоставляемыми контейнером.
Реализация
Что ж, приступим к самому боту.
Поскольку мы пишем на спринговом стеке, то мы можем не создавать свой контейнер команд, а воспользоваться уже существующим в спринге. Их можно не сканировать, а получить из IoC контейнера.
Более самостоятельные разработчики могут сразу приступить к чтению кода.
Здесь же я разберу именно чтение команд, хотя в самом репозитории есть ещё пара интересных моментов, которые вы можете рассмотреть самостоятельно.
Реализация очень похожа на бота через Reflections, поэтому аннотации те же.
В отличие от прошлого примера здесь уже используется более высокий уровень абстракции для интерфейсов, что, конечно же, хорошо. Также нам не нужно самим создавать экземпляры команд.
Подведём итоги
Только вам решать, что лучше подойдёт под вашу задачу. Я разобрал условно три случая для примерно похожих ботов:
Если вы приверженец более чистого подхода с нуля даже без JPA, то посмотрите на этого бота, который работает через JDBC через ВК и Телеграм.
Там вы увидите много записей вида:
Но код имеет двухгодичную давность, так что не советую оттуда брать все паттерны. И вообще я бы не рекомендовал таким заниматься вовсе (работу через JDBC).
Также лично мне не особо нравится работать напрямую с Hibernate. Я уже имел печальный опыт писать DAO и HibernateSessionFactoryUtil (те, кто писал, поймут, о чём я).
Что касается самой статьи, я пытался писать кратко, но достаточно, чтобы, имея в руках только эту статью, вы могли начать разработку. Всё-таки это не глава в книге, а статья на Хабре. Глубже изучить аннотации и вообще рефлексию вы сможете сами, например, создавая того же бота.
Всем удачи! И не забывайте о промокоде HABR, который дает дополнительную скидку 10% к той, что указана на баннере.
Аннотации в Java. Как создать свою аннотацию
Объясняю на пальцах, что такое аннотации в Java, а также рассказываю как создать свою аннотацию и обработчик к ней в Java.
Аннотации – это своеобразные маркеры, с помощью которых программист указывает компилятору Java и средствам разработки, что делать с участками кода помимо исполнения. Аннотировать можно переменные, параметры, классы, пакеты. Можно писать свои аннотации или использовать стандартные – встроенные в Java.
Обратите внимание, сама аннотация никак не влияет на переопределение метода, но позволяет контролировать успешность переопределения при компиляции или сборке. Мы защитили участок кода от неприметной ошибки, на поиск которой в большой программе ушли бы часы. Это лишь одно из многих применений аннотаций.
Структура аннотации
Параметры задаются как методы у интерфейсов, только без аргументов. А ключевое слово default — говорит про то, что метод по умолчанию будет возвращать определённое значение.
Так как мы не сконфигурировали аннотацию, то она может применяться к чему угодно: к классам, методам, атрибутам и т. п. Для того чтобы ограничить использование аннотации, её нужно разметить аннотациями 😄
Аннотация @Target позволяет ограничить область применения:
Если нужно, что бы ваша аннотация использовалась больше чем для одного типа, укажите @Target следующим образом:
Помимо @Target есть еще несколько аннотаций, для настройки:
@Retention определяет в каком жизненном цикле кода аннотация будет доступна.
@Inherited позволяет реализовать наследование аннотаций родительского класса классом-наследником
Обработчик аннотации
Но магии в программировании нет, и аннотации сами по себе ничего не делают, нужно написать обработчик аннотации.
А наша аннотация должна сгенерировать нам класс в том же пакете с названием SimpleFields :
Для этого создаем аннотацию @FieldNames :
Аннотация @SupportedAnnotationTypes отвечает за указание аннотации для которой этот обработчик создается.
Аннотация @AutoService упрощает создание манифеста. Но для нее нужно добавить новую зависимость
Дебажить обработчик обычным способом у вас не получится. Как дебажить обработчики я писал в отдельной статье: Дебаг приложения на этапе компиляции IntelliJ IDEA
Нам необходимо получить все классы, которые помечены нашей аннотацией.
Осталось написать логику обработки нашей аннотации. Логика будет такая:
Создадим 2 новых класса. ClassDto будет содержать информацию, необходимую для генерации нового класса. Класс FieldDto будет отвечать за информацию необходимую для создания public static final String полей. Лучше смотреть на примерах, так сложно объяснить.
Чтобы преобразовать имя переменной numberTwo в имя статической переменной NUMBER_TWO нам понадобиться еще одна зависисомость:
Теперь нам надо заполнить класс ClassDto информацией:
Рассмотрим класс генератор:
Проверка работы
Чтобы проверить работу нашей аннотации, создадим новый проект, в который добавим нашу библиотеку в виде зависимости.
Заключение
Этих базовых знаний будет достаточно, чтобы начать разбираться в создании собственных обработчиков для Java аннотаций.
Как написать аннотацию на Java за 5 шагов
Разбираемся с аннотациями в Java: пошаговое руководство.
AD Design Award 2021
Аннотации нужны для описания метаданных — дополнительной информации о программных элементах. Они могут использоваться, например, для конфигурирования программного окружения или для выдачи указаний компилятору: чтобы он учитывал эти аннотации при проверке ошибок или подавлении предупреждений.
Аннотации — удобный способ собрать данные о сущности и правила их обработки в одном месте.
В Java есть встроенные аннотации. Например:
Но нам мало встроенных аннотаций. Поэтому научимся создавать свои: начнём с простой маркерной и за пять шагов дойдём до вершины мастерства — повторяющейся аннотации.
Текущий релиз Java SE с долгосрочной поддержкой (LTS) — Java 11. Поэтому описание синтаксиса и примеры ниже даны для этой версии языка.
Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.
Шаг нулевой, подготовительный
Формулируем задачу и описываем контекст
Потренируемся в написании аннотаций на примере Telegram-каналов: недавно мне попался очередной опрос от создателей мессенджера, в котором был и вопрос о числе подписок на каналы. Внезапно оказалось, что у меня их больше 30, самых разных, — вот пусть и поработают подопытными кроликами.
Создадим аннотации, показывающие, насколько интересен контент канала и какого рода информация там публикуется, ну а дальше как пойдёт 😀
Шаг первый
Создаём каркас аннотации
Аннотации похожи на интерфейсы. Если вы пока не очень ориентируетесь в интерфейсах, освежите свои знания о них в этой статье, перед тем как мы продолжим.
Они не просто похожи на интерфейсы: под капотом JVM (виртуальная машина Java) преобразует элементы аннотации в методы интерфейса, а саму аннотацию — в имплементацию (реализацию) этого интерфейса.
Вот как выглядит объявление простейшей аннотации для отметки особо интересных каналов:
Здесь, кроме заголовка, нет ничего полезного. Такие аннотации ещё называют маркерными — они действительно просто маркируют, обозначают какой-то признак.
При использовании маркерных аннотаций можно опускать круглые скобки после названия.
В аннотациях могут быть:
Шаг второй
Добавляем обязательные элементы
Напишем аннотацию, которой будем обозначать каналы о хобби. У неё будет один обязательный элемент — собственно описание хобби: фотография, музыка, путешествия и тому подобное.
После названия элемента обязательны круглые скобки — как для методов в интерфейсе. Набор типов для элементов ограничен, можно использовать только:
Это значит, что можно определить элемент таким образом:
Но нельзя, например, так:
final здесь недопустим, потому что конфликтует с неявным abstract. Это выглядит логичным, если вспомнить, что final запрещает переопределять метод, а abstract, напротив, требует реализации в наследниках. Исключение — только если наследник тоже абстрактный.
Каждому обязательному элементу необходимо задать значение — на то они и обязательные. Для определения элемента-массива указываются фигурные <> скобки. Если в массиве только один элемент (как во втором примере с музыкальным каналом), скобки можно опустить.
Где-то в коде вам может попасться аннотация, у которой в скобках просто написано значение элемента без названия, вот так:
Можете быть уверены, что у этой аннотации есть элемент с особым названием:
Необязательные элементы допустимы, но если захотите изменить их значения по умолчанию, придётся явно указать и название элемента value:
Шаг третий
Добавляем необязательные элементы
Сделаем необязательными все элементы, кроме kind. Для этого всем остальным элементам нужно задать значения по умолчанию.
Вот как это делается:
В качестве дефолтного значения может использоваться только константное НЕ null-значение. То есть вот такие объявления компилятор не пропустит:
Зато пустую строку в качестве значения по умолчанию использовать можно, вот так:
Текста по сравнению с предыдущим пунктом стало меньше, потому что значения необязательных элементов можно не указывать. В этом случае будут использоваться значения по умолчанию.
Однако никто не запрещает указать своё значение вместо дефолтного. В примере выше каналу NationalGeographicChannel мы установили не равный единице элемент level, а ещё задали для обоих классов непустые множества тегов — хоть это и необязательный элемент.
Кроме элементов со значениями по умолчанию, в аннотациях можно использовать константы. Они, как и в интерфейсах, неявно public, static и final. Вот, к примеру, аннотация для описания времени на чтение каналов, в которой заданы константы для минимального и максимального тайминга:
Шаг четвёртый
Определяем область действия
К чему применима аннотация
До этого шага мы использовали аннотации только для классов, но они применимы к интерфейсам, методам, параметрам класса, локальным переменным и не только. За область применимости аннотации отвечает другая аннотация — @Target. Полный список доступных значений для её единственного элемента value есть в официальной документации.
Если не задавать @Target, аннотацию можно использовать для любых программных элементов.
Мы же для примера напишем аннотацию, которая будет применима к методам и конструкторам классов. Предположим, она будет показывать, что после выполнения таких конструкторов и таких методов нужно будет отправить кому-то тревожное сообщение. Реализацию обработчика оставим за кадром, а аннотация будет выглядеть так:
Если теперь попробуем написать @Alarm перед названием самого класса SecureChannel, получим ошибку компиляции, потому что в @Target не включено значение для типа элемента «класс».
Когда аннотация доступна
Если мы и правда хотим прямо во время выполнения программы искать какие-то помеченные аннотацией @Alarm методы, одним только указанием @Target не обойтись.
Есть ещё одна аннотация для описания аннотаций — это @Retention. Она определяет доступность в течение жизненного цикла программы. У её единственного элемента value всего три доступных значения:
Воспользуемся новыми знаниями и допишем нашу тревожную аннотацию:
Теперь она будет доступна в рантайме, что и требовалось.