Получить все значения сгенерированной аннотации в процессоре аннотаций

У меня есть поле VariableElement, которое аннотировано сгенерированной аннотацией (поэтому я не могу использовать field.getAnnotation(annotationClass)). Мне нужно передать все параметры этой аннотации.

Обратите внимание, что под «сгенерированной аннотацией» я подразумеваю, что буквально сам класс аннотаций (не аннотированный) был сгенерирован процессором аннотаций. Аннотируемое поле/класс находится в рукописном исходном коде.

Это не выглядело так сложно, пока я придумал это:

for (AnnotationMirror annotation : field.getAnnotationMirrors()) {
    Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValueMap = annotation.getElementValues();

    messager.printMessage(Diagnostic.Kind.WARNING, annotation.toString() + ":" + annotationValueMap.toString());
}

Я думал, что это сделает это, но вывод для поля следующий:

@MyAnnotation:{}

Итак, процессор распознает, что поле аннотировано, но я не могу получить доступ к переданным параметрам. Несмотря на то, что поле определенно аннотировано и передает параметры с аннотацией (это необходимо, поскольку аннотация определяет обязательные параметры, а не значения по умолчанию):

@MyAnnotation(max = 387, min = 66876, ...)
private Integer myField;

Вот сгенерированный код аннотации:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface MyAnnotation {
  int max();

  boolean allowAuto();

  int min();
}

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

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

Colin Alworth 16.03.2019 20:11

И вы создаете аннотацию до, читая ее как зеркало? Ожидается, что это произойдет не только в одном и том же проходе, но и в том же правильном порядке? Я уверен, что это никогда не сработает в обратном порядке, и думаю, что маловероятно, что это можно было бы сделать даже в одном и том же проходе (поскольку в аннотации еще даже нет «полей», по крайней мере, не статического типа, поэтому значения карты не могут иметь тип, даже если они используются в источниках).

Colin Alworth 16.03.2019 20:12

@ColinAlworth Ваш первый комментарий верен - класс аннотаций создается в том же раунде/проходе в одном и том же процессоре. Я создаю классы аннотаций, прежде чем читать их как зеркало, и этот порядок определен. В настоящее время это происходит в том же раунде, поскольку класс, использующий аннотацию, не создается и, следовательно, не присутствует в последующих раундах.

Namnodorel 16.03.2019 20:20

Мы не в состоянии быть здесь на 100% уверены, но я понимаю, что в течение раунда сгенерированные источники не будут скомпилированы, а вместо этого вам нужно дождаться окончания раунда, а до этого поля для этих вновь испущенных типов будет недоступен, даже в таких случаях. Ожидание следующего раунда не означает, что тип не будет присутствовать в раунде, просто он не будет предлагаться напрямую, вы все равно можете прочитать его из объекта Elements и т. д. См. пример auto-common BasicAnnotationProcessor , намеренно избегая обработки незавершенных классов, пока они не будут готовы.

Colin Alworth 16.03.2019 20:27

@ColinAlworth Вероятно, вам следует опубликовать это как ответ :) annotation.toString() просто выводит буквальную аннотацию, и ему все равно, существует ли тип или где он находится. А вот тип Annotation (и его методы) видимо нужен для определения передаваемых параметров (правда не понимаю зачем). Использование BasicAnnotationProcessor для отсрочки обработки аннотаций решило проблему.

Namnodorel 17.03.2019 15:38
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
5
341
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Используйте getAnnotation(MyAnnotation.class), доступный в VariableElement.

в вашем примере кода вы можете сделать это, чтобы получить параметры min и max

MyAnnotation myAnnotation= field.getAnnotation(MyAnnotation.class);
int max = myAnnotation.max();
int min = myAnnotation.min();

это будет работать, если члены аннотации не вернут значение class/class[], в котором вы получите исключение, если попытаетесь получить значение с помощью этого метода.

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

Как читать значения Class[] из вложенной аннотации в процессоре аннотаций

Или с помощью зеркал аннотаций

for (AnnotationMirror annotation : field.getAnnotationMirrors()) {
    Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValueMap = annotation.getElementValues();
    annotationValueMap.forEach((element, annotationValue) -> {
        messager.printMessage(Diagnostic.Kind.WARNING, element.getSimpleName().toString() + ":" + annotationValue.getValue());
    });
}

Если у вас есть более одной аннотации на поле, вы можете перебрать зеркала аннотаций и использовать проверку types.isSameType(annotationMirror.getAnnotationType(), elements.getTypeElement(MyAnnotation.class.getName()).asType()), чтобы найти интересующую вас аннотацию.

Я не могу использовать field.getAnnotation(annotationClass), потому что аннотация, о которой идет речь, создается. Таким образом, у меня нет доступа к объекту класса во время компиляции процессора. Ваш второй пример ничего не выводит для меня (потому что карта почему-то пуста).

Namnodorel 15.03.2019 17:02

@Namnodorel это также работает для меня в сгенерированном классе, вы можете поделиться с нами реализацией аннотации.

Ahmad Bawaneh 15.03.2019 17:09

@Namnodorel ты пробовал использовать @Retention(RetentionPolicy.RUNTIME) вместо SOURCE?

Ahmad Bawaneh 15.03.2019 17:21

У меня сейчас есть, и нет никакой разницы. Хотя было бы очень странно, если бы это было так, учитывая, что весь смысл SOURCE должен использоваться процессорами аннотаций.

Namnodorel 15.03.2019 17:28

Интересно, что сохранение = ИСТОЧНИК означает, как и следовало ожидать, что аннотация присутствует только в файле .java, а не в файле .class. Это по-прежнему влияет на обработчики аннотаций двумя способами: во-первых, если ваш компилятор (eclipse, gradle и т. д.) является инкрементным, он может не выполнять повторный анализ всех источников, а вместо этого использовать байт-код, а во-вторых, если вы в конечном итоге ссылаетесь на аннотацию в отдельном модуль, то он уже скомпилирован, поэтому аннотация, конечно, будет отсутствовать. ИСТОЧНИК имеет смысл только тогда, когда вы на 100% уверены, что процессор должен видеть его только тогда, когда этот конкретный файл изменяется, что меньше, чем вы думаете.

Colin Alworth 15.03.2019 17:40

@ColinAlworth Интересно ... Хотя, если бы это было проблемой, аннотация вообще не распознавалась бы, вместо того, чтобы просто не иметь элементов, или будет?

Namnodorel 16.03.2019 09:42

Хорошая точка зрения. Я думаю, нам нужно увидеть больше вашего кода, если этот ответ не решит вашу проблему, потому что я также успешно использовал обе стратегии в этом ответе.

Colin Alworth 16.03.2019 14:09

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

Namnodorel 16.03.2019 19:44

Что вы подразумеваете под созданием аннотации? Другой процессор создает аннотацию на лету, а затем читает ее, когда вы ее компилируете? Или просто сгенерировать использование аннотации (я определенно сделал это). Обратите внимание, что до тех пор, пока код не будет успешно скомпилирован, некоторые части будут отсутствовать — «ErrorType» и другие способы определить это. Я думаю, вам следует добавить больше деталей к вашему вопросу, чтобы мы могли лучше помочь.

Colin Alworth 16.03.2019 19:52

@ColinAlworth Сам класс Annotation создается (и записывается) в том же процессоре, но до доступа к любому аннотированному полю. Я включил это в вопрос, но, видимо, слишком двусмысленно. попробую перефразировать.

Namnodorel 16.03.2019 20:04

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

Colin Alworth 16.03.2019 20:08
Ответ принят как подходящий

Напомним, что обработчики аннотаций запускаются как часть компилятора в шагах, называемых «раундами». Этот процесс выполняется итеративно до тех пор, пока не останется нового кода для компиляции, а затем процессоры получат последний шанс запуститься (не обязательно для этого ответа, но полезно для большего контекста). В каждом раунде только вновь созданные типы напрямую передаются процессору для проверки.

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

По этому вопросу мы сталкиваемся здесь с конфликтом - скомпилированы некоторые исходники Java, которые используют аннотацию которого еще не существует. Процессор сначала создает аннотацию, а затем пытается прочитать вновь созданную аннотацию из этих частично скомпилированных источников. К сожалению, пока аннотация не будет скомпилирована, мы не можем ее прочитать. Вместо этого нам нужно дождаться следующего раунда (после компиляции самой аннотации), затем вернуться к тому классу, компиляция которого завершена, и изучить его.

Это можно реализовать самостоятельно без особых проблем, но чаще всего проще всего положиться на проект google/auto (в частности, на библиотеку auto-common, см. https://github.com/google/auto/tree/master/common) и расширить их класс BasicAnnotationProcessor. Одной из приятных функций, которые он поддерживает, является автоматическая проверка типов и проверка наличия каких-либо проблем с компиляцией - если они есть, они откладываются до более позднего раунда, чтобы вы могли справиться с ними без каких-либо проблем с разрешением типов.

Другие вопросы по теме