Подтвердить что ты не робот

AnnotationProcessor использует несколько исходных файлов для создания одного файла

У меня есть два класса с методами, и я хочу объединить методы двух классов с одним классом.

@Service("ITestService")
public interface ITest1
{
   @Export
   void method1();
}

@Service("ITestService")
public interface ITest2
{
   @Export
   void method2();
}

Результат должен быть:

public interface ITestService extends Remote
{
  void method1();
  void method2();
}

Первый запуск моего AnnotationProcessor генерирует правильный вывод (поскольку RoundEnvironment содержит оба класса).

Но если я отредактирую один из классов (например, добавив новый метод), RoundEnviroment содержит только отредактированный класс, и поэтому результат будет follwing (добавление newMethod() в интерфейс ITest1)

public interface ITestService extends Remote
{
  void method1();
  void newMethod();
}

Теперь метод method2 отсутствует. Я не знаю, как исправить мою проблему. Есть ли способ (Enviroment), доступ ко всем классам в проекте? Или есть другой способ решить эту проблему?

Код для генерации класса довольно длинный, поэтому здесь приводится краткое описание того, как я генерирую класс. Я повторяю Элементы с помощью env.getElementsAnnotatedWith(Service.class) и извлекаю методы и записываю их в новый файл с помощью:

FileObject file = null;
file = filer.createSourceFile("com/test/" + serviceName);
file.openWriter().append(serviceContent).close();
4b9b3361

Ответ 1

- Вариант 1 - Ручная компиляция из командной строки ---

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

Я провел несколько тестов с двумя интерфейсами, в которых один (A) зависит от другого (extends), и у меня есть следующие сценарии:

  • Если я попрошу компилятор явно скомпилировать только интерфейс, который имеет зависимость (A), передав полный путь к java файлу в командной строке и добавив выходную папку в путь к классам, только интерфейс, который я передал обрабатывается командная строка.
  • Если я явно компилирую только (A) и не добавляю выходную папку в путь к классам, компилятор по-прежнему обрабатывает только интерфейс (A). Но это также дает мне предупреждение: Implicitly compiled files were not subject to annotation processing.
  • Если я использую * или передаю оба класса компилятору в командную строку, тогда я получаю ожидаемый результат, оба интерфейса обрабатываются.

Если вы установите подробный компилятор, вы получите сообщение об ошибке, показывающее, какие классы будут обрабатываться в каждом раунде. Это то, что я получил, когда я явно передал интерфейс (A):

Round 1:
input files: {com.bearprogrammer.test.TestInterface}
annotations: [com.bearprogrammer.annotation.Service]
last round: false

И это то, что у меня есть, когда я добавил оба класса:

Round 1:
input files: {com.bearprogrammer.test.AnotherInterface, com.bearprogrammer.test.TestInterface}
annotations: [com.bearprogrammer.annotation.Service]
last round: false

В обоих случаях я вижу, что компилятор анализирует оба класса, но в другом порядке. В первом случае (добавлен только один интерфейс):

[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\TestInterface.java]]
[parsing completed 15ms]
[search path for source files: src\main\java]
[search path for class files: ...]
[loading ZipFileIndexFileObject[lib\processor.jar(com/bearprogrammer/annotation/Service.class)]]
[loading RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]]
[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]]

Для второго случая (все добавленные интерфейсы):

[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]]
...
[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\TestInterface.java]]
[search path for source files: src\main\java]
[search path for class files: ...]
...

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

Подробнее о процессе компиляции см. в Обзор компиляции. Что явно не говорит о том, какие файлы собираются для обработки.

Решение в этом случае было бы всегда добавлять все классы в команду для компилятора.

--- Вариант 2 - Компиляция из Eclipse ---

Если вы компилируете из Eclipse, инкрементная сборка приведет к сбою вашего процессора (не проверял его). Но я бы подумал, что вы можете обойти это с просьбой о чистой сборке (Project > Clean..., также не протестировали его) или написать сборку Ant, которая всегда очищает каталог классов и настраивает Ant Builder из Eclipse.

--- Вариант 3 - Использование инструментов построения ---

Если вы используете какой-либо другой инструмент сборки, например Ant, Maven или Gradle, лучшим решением будет создание источника на отдельном шаге, чем ваша компиляция. Вам также понадобится, чтобы ваш процессор был скомпилирован на отдельном предыдущем шаге (или отдельный подпроект, если использовать мультипроекты в Maven/Gradle). Это был бы лучший сценарий, потому что:

  • Для этапа обработки вы всегда можете выполнить полную "компиляцию" без фактической компиляции кода (используя параметр -proc:only из javac только для обработки файлов)
  • Сгенерированный исходный код на месте, если вы использовали Gradle, он был бы достаточно умным, чтобы не перекомпилировать сгенерированные исходные файлы, если они не изменились. Ant, и Maven только перекомпилирует необходимые файлы (сгенерированные и их зависимости).

Для этого третьего варианта вы также можете настроить Ant build script, чтобы сгенерировать эти файлы из Eclipse как строителя, который работает до вашего Java-конструктора. Создайте исходные файлы в какой-либо специальной папке и добавьте это в свой путь к classpath/buildpath в Eclipse.

Ответ 2

NetBeans @Сообщения аннотация генерирует один файл Bundle.java для всех классов в одном пакете. Он корректно работает с инкрементной компиляцией благодаря следующему трюку в обработчике комментариев :

Set<Element> toProcess = new HashSet<Element>();
for (Element e : roundEnv.getElementsAnnotatedWith(Messages.class)) {
  PackageElement pkg = findPkg(e);
  for (Element elem : pkg.getEnclosingElements()) {
    if (elem.getAnnotation(Message.class) != null) {
      toProcess.add(elem);
    }
  }
}
// now process all package elements in toProcess 
// rather just those provided by the roundEnv

PackageElement findPkg(Element e) {
  for (;;) {
    if (e instanceof PackageElement) {
      return (PackageElement)e;
    }
    e = e.getEnclosingElement();
  }
}

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

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