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

У меня есть поле 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();
}

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


person Namnodorel    schedule 14.03.2019    source источник
comment
Для дальнейшего подтверждения вопроса - аннотация создается, а аннотированный тип - нет? Итак, до компиляции источник существовал с несуществующей аннотацией, а затем аннотация создается в том же проходе, что вы читаете свойства аннотации? Или это разные проходы, или разные процессоры?   -  person Colin Alworth    schedule 16.03.2019
comment
И вы создаете аннотацию перед чтением ее как зеркала? Ожидается, что это произойдет не только за один проход, но и в правильном порядке? Я уверен, что это никогда не сработает в обратном порядке, и думаю, что маловероятно, что это можно было бы даже сделать в одном проходе (поскольку в аннотации еще нет полей, по крайней мере, не статического типа, поэтому значения карты не может иметь типа, даже если они используются в источниках).   -  person Colin Alworth    schedule 16.03.2019
comment
@ColinAlworth. Ваш первый комментарий верен - класс аннотации создается в том же цикле / проходе в одном и том же процессоре. Я создаю классы аннотаций, прежде чем читать их как зеркало, и этот порядок определен. В настоящее время это происходит в одном и том же раунде, поскольку класс, использующий аннотацию, не генерируется и, следовательно, не присутствует в последующих раундах.   -  person Namnodorel    schedule 16.03.2019
comment
У нас нет возможности быть здесь на 100% уверенным, но я понимаю, что в рамках цикла сгенерированные источники не будут компилироваться, а вместо этого вам нужно дождаться завершения цикла, а до тех пор поля в этих недавно созданных типах не будет доступен даже в таких случаях. Ожидание следующего раунда не означает, что тип не будет присутствовать в раунде, просто он не будет предлагаться напрямую, вы все равно можете прочитать его из объекта Elements и т. Д. См. В качестве примера BasicAnnotationProcessor auto-common , намеренно избегая обработки неполных классов, пока они не будут готовы.   -  person Colin Alworth    schedule 16.03.2019
comment
@ColinAlworth Вам, вероятно, следует опубликовать это как ответ :) annotation.toString() просто выводит буквальную аннотацию, и не заботится о том, существует ли тип или где он находится. Но тип аннотации (и его методы), по-видимому, необходимы для определения переданных параметров (хотя я не понимаю, почему). Использование BasicAnnotationProcessor для отсрочки обработки аннотаций решило проблему.   -  person Namnodorel    schedule 17.03.2019


Ответы (2)


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

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

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

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

person Colin Alworth    schedule 18.03.2019

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

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

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

это будет работать, если члены аннотации не вернут значение 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()), чтобы найти интересующую вас аннотацию.

person Ahmad Bawaneh    schedule 15.03.2019
comment
Я не могу использовать field.getAnnotation(annotationClass), потому что соответствующая аннотация создана. Таким образом, у меня нет доступа к объекту Class во время компиляции процессора. Ваш второй пример ничего не выводит для меня (потому что карта по какой-то причине пуста). - person Namnodorel; 15.03.2019
comment
@Namnodorel, это также работает для меня в сгенерированном классе, вы можете поделиться с нами реализацией аннотации. - person Ahmad Bawaneh; 15.03.2019
comment
@Namnodorel вы пробовали использовать @Retention(RetentionPolicy.RUNTIME) вместо SOURCE? - person Ahmad Bawaneh; 15.03.2019
comment
У меня сейчас есть, и разницы нет. Хотя было бы очень странно, если бы это было так, учитывая, что весь смысл SOURCE должен использоваться обработчиками аннотаций. - person Namnodorel; 15.03.2019
comment
Интересно, что retention = SOURCE означает, как и следовало ожидать, аннотация присутствует только в файле .java, а не в файле .class. Это по-прежнему влияет на процессоры аннотаций двумя способами: во-первых, если ваш компилятор (eclipse, gradle и т. Д.) Является инкрементным, он может не повторно анализировать все источники, а вместо этого использовать байт-код, а во-вторых, если вы в конечном итоге ссылаетесь на аннотацию в отдельном модуль, то он уже скомпилирован, поэтому аннотации, конечно же, не будет. SOURCE имеет смысл только тогда, когда вы на 100% уверены, что процессор должен видеть его только тогда, когда этот точный файл изменяется, что верно меньше, чем вы думаете. - person Colin Alworth; 15.03.2019
comment
@ColinAlworth Интересно ... Хотя, если бы это была проблема, аннотация не распознавалась бы вообще, вместо того, чтобы просто не иметь элементов, или нет? - person Namnodorel; 16.03.2019
comment
Хорошая точка зрения. Я думаю, нам нужно увидеть больше вашего кода, если этот ответ не решит вашу проблему, потому что я также успешно использовал обе стратегии в этом ответе. - person Colin Alworth; 16.03.2019
comment
@ColinAlworth Когда вы их использовали, создавали ли вы аннотацию? Я думаю, что это как-то связано с проблемой - хотя AnnotationMirror должен работать с исходным кодом, а не с байт-кодом. - person Namnodorel; 16.03.2019
comment
Что вы имеете в виду, создав аннотацию? Создает ли аннотацию на лету другой процессор, а затем она читается, когда вы ее компилируете? Или просто создать аннотацию (я определенно это сделал). Обратите внимание, что до тех пор, пока код не будет успешно скомпилирован, некоторые части будут отсутствовать - ErrorType и другие способы определить это. Я думаю, вам следует добавить больше деталей к своему вопросу, чтобы мы могли лучше помочь. - person Colin Alworth; 16.03.2019
comment
@ColinAlworth Сам класс аннотации создается (и записывается) в том же процессоре, но до обращения к любому аннотированному полю. Я включил это в вопрос, но, видимо, слишком двусмысленно. Попробую перефразировать. - person Namnodorel; 16.03.2019
comment
Спасибо, мне очень жаль, что я не понял этого раньше - я думаю, что это, вероятно, самая важная часть вопроса, и что вы, возможно, захотите зайти так далеко, чтобы привести краткий пример того, как это делается, поскольку это в противном случае кажется довольно далеким от сорняков. Я подозреваю, что даже если сама аннотация еще не скомпилирована, нет возможности прочитать ее свойства, поэтому вам придется либо разделить это на несколько проходов одного и того же процессора, либо на несколько процессоров ... - person Colin Alworth; 16.03.2019