Обработка аннотаций, RoundEnvironment.processingOver()

Читая код процессора пользовательских аннотаций в Java, я заметил этот фрагмент кода в процессоре process метод:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  if (!roundEnv.errorRaised() && !roundEnv.processingOver()) {
    processRound(annotations, roundEnv);
  }
  return false;
}

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

Я попробовал код выше таким образом:

if (!roundEnv.errorRaised() && !roundEnv.processingOver()) {
    processRound(annotations, roundEnv);
}
return false;

& сюда:

if (!roundEnv.errorRaised()) {
    processRound(annotations, roundEnv);
}
return false;

но я не мог заметить каких-либо изменений в поведении процессора. Я получаю !roundEnv.errorRaised() проверить, но я не вижу, как это !roundEnv.processingOver() любой полезный.

Я хотел бы знать случаи использования, где это полезно использовать roundEnv.processingOver() при обработке определенного раунда.

1 ответ

Решение

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

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

Последовательности компиляции будут выглядеть так:

  1. Первичный раунд (возможно, с генерацией кода);
  2. Несколько дополнительных раундов генерации кода; новые раунды будут происходить до тех пор, пока обработчики аннотаций не сгенерируют ничего;
  3. Финальный раунд; код, сгенерированный во время этого раунда, не будет подвергаться обработке аннотаций.

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

Эта замечательная последовательность позволяет использовать подход, популярный в таких библиотеках, как Dagger2 и Android-Annotated-SQL: ссылаться на еще не существующий класс в исходном коде и позволить процессору аннотаций генерировать его во время компиляции:

// this would fail with compilation error in absence of Dagger2
// but annotation processor will generate the Dagger_DependencyFactory
// class during compilation
Dagger_DependencyFactory.inject(this);

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


Итак, как все это относится к процессору аннотаций Spring в вашем вопросе?

TL; DR: код в вашем вопросе глючит.

Правильный способ использовать эти методы так:

за errorRaised:

  1. Если ваш процессор генерирует новые общедоступные классы (которые могут быть использованы в пользовательском коде "заранее", как описано выше), вы должны быть сверхустойчивыми: продолжать генерировать, игнорировать пропущенные биты и несоответствия, когда это возможно, и игнорировать errorRaised, Это гарантирует, что вы оставите как можно меньше пропущенных вещей к тому времени, когда Javac перейдет в режим сообщения об ошибках.
  2. Если ваш код не генерирует новые общедоступно видимые классы (например, поскольку он создает только закрытые для пакета классы, а другой код будет рефлексивно искать их во время выполнения, см. ButterKnife), тогда вам следует проверить errorRaised КАК МОЖНО СКОРЕЕ, и немедленно завершите работу, если оно вернет true. Это упростит ваш код и ускорит ошибочные компиляции.

за processingOver:

  1. Если текущий раунд не последний (processingOver возвращает false), попытайтесь сгенерировать как можно больше выходных данных; игнорировать пропущенные типы и методы в пользовательском коде (предположим, что какой-то другой процессор аннотаций может сгенерировать их в следующих раундах). Но все же попытайтесь генерировать как можно больше, в случае, если это может понадобиться для других процессоров аннотаций. Например, если вы запускаете генерацию кода для каждого класса, отмеченного @EntityВы должны перебрать эти классы и попытаться сгенерировать код для каждого, даже если в предыдущих классах были ошибки или отсутствующие методы. Лично я просто упаковываю каждую отдельную единицу поколения в try-catch и проверяю processingOver: если оно ложно, игнорируйте ошибки и продолжайте перебирать аннотации и генерировать код. Это позволяет Javac нарушать циклические зависимости между кодом, генерируемыми различными процессорами аннотаций, выполняя их до полного удовлетворения.
  2. Если текущий раунд не последний (processingOver возвращает false), и некоторые из комментариев предыдущего раунда не были обработаны (я помню их всякий раз, когда обработка завершается неудачей из-за исключительной ситуации), повторите обработку для них.
  3. Если текущий раунд последний (processingOver возвращает true), посмотрите, есть ли аннотации, которые еще не обработаны. Если это так, сбой компиляции (только во время последнего раунда!)

Приведенная выше последовательность является предполагаемым способом использования processingOver,

Некоторые процессоры аннотаций используют processingOver немного по-другому: они буферизуют код, сгенерированный во время каждого раунда, и фактически записывают его в Filer во время последнего тура. Это позволяет разрешать зависимости от других процессоров, но не позволяет другим процессорам находить код, сгенерированный "осторожными" процессорами. Это немного неприятная тактика, но если сгенерированный код не предназначен для ссылки в другом месте, я думаю, это нормально.

И есть процессоры аннотаций, такие как вышеупомянутый сторонний валидатор конфигурации Spring: они неправильно понимают некоторые вещи и используют API в стиле "Обезьяна и ключ".

Чтобы лучше понять суть, установите Dagger2 и попытайтесь ссылаться на классы, сгенерированные Dagger, в классах, используемых другим процессором аннотаций (предпочтительно таким образом, чтобы этот процессор разрешал их). Это быстро покажет вам, насколько хорошо эти процессоры справляются с многоходовой компиляцией. Большинство просто разбило бы Javac за исключением. Некоторые из них будут выплевывать тысячи ошибок, заполняя буферы отчетов об ошибках IDE и запутывая результаты компиляции. Очень немногие будут правильно участвовать в многоэтапной компиляции, но все равно выложат много ошибок, если это не удастся.

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


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

К сожалению, рассматриваемый процессор выглядит заброшенным (без коммитов с 2015 года), но автор активен на Github, поэтому, возможно, вы сможете сообщить о проблеме им.

А пока я предлагаю вам поучиться у хорошо продуманных процессоров аннотаций, таких как Google Auto, Dagger2 или мой крошечный исследовательский проект.

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