Замена кода процессором аннотации
Я пытаюсь написать обработчик аннотаций для вставки методов и полей в класс... и документация очень скудная. Я не ухожу далеко и не знаю, правильно ли я к нему подхожу.
Среда обработки обеспечивает Filer
Объект, который имеет удобные методы для создания новых исходных файлов и файлов классов. Они работают нормально, но затем я попытался выяснить, как читать существующие исходные файлы, и все, что он предоставляет, это "getResource". Итак, в моей реализации процессора я сделал это:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
for (TypeElement te : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
FileObject in_file = processingEnv.getFiler().getResource(
StandardLocation.SOURCE_PATH, "",
element.asType().toString().replace(".", "/") + ".java");
FileObject out_file = processingEnv.getFiler().getResource(
StandardLocation.SOURCE_OUTPUT, "",
element.asType().toString().replace(".", "/") + ".java");
//if (out_file.getLastModified() >= in_file.getLastModified()) continue;
CharSequence data = in_file.getCharContent(false);
data = transform(data); // run the macro processor
JavaFileObject out_file2 = processingEnv.getFiler().createSourceFile(
element.asType().toString(), element);
Writer w = out_file2.openWriter();
w.append(data);
w.close();
}
}
} catch (Exception e) {
e.printStackTrace();
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
return true;
}
Мое первое затруднение - я не могу не чувствовать, что element.asType().toString().replace(".", "/") + ".java"
(получить полное имя типа и преобразовать его в пакет и путь к исходному файлу) не очень хороший способ решить проблему. Остальная часть API слишком перегружена, но, похоже, нет удобного метода для извлечения исходного исходного кода.
Реальная проблема в том, что тогда компилятор самопроизвольно расстраивается из-за второго исходного файла в выходном каталоге ("ошибка: дубликат класса"), и теперь я застрял.
Я уже написал остальную часть этого - лексер и синтаксический анализатор макросов и еще много чего для вычисления некоторых данных и вставки значений и методов поля - но он действует как начальный шаг вне компилятора. За исключением того факта, что исходные файлы не могут иметь расширение.java (чтобы компилятор их не видел), это прекрасно работает. Затем я услышал, что аннотации могут генерировать код, который, как я полагаю, будет более правильным и удобным, но я не могу найти много рекомендаций по этому поводу.
3 ответа
Цель процессора аннотаций - позволить разработчику добавлять новые классы, а не заменять существующие. При этом существует ошибка, которая позволяет добавлять код в существующие классы. Project Lombok использовал это, чтобы добавить getter и setter (среди прочего) в ваши скомпилированные классы Java.
Подход, который я использовал для "замены" методов / полей, либо расширяется, либо делегируется входному классу. Это позволяет вам переопределять / переадресовывать вызовы в целевой класс.
Так что, если это ваш входной класс:
InputImpl.java:
public class InputImpl implmements Input{
public void foo(){
System.out.println("foo");
}
public void bar(){
System.out.println("bar");
}
}
Вы можете сгенерировать следующее, чтобы "заменить" его:
InputReplacementImpl.java:
public class InputReplacementImpl implmements Input{
private Input delegate;
//setup delegate....
public void foo(){
System.out.println("foo replacement");
}
public void bar(){
delegate.bar();
}
}
Напрашивается вопрос, как вы ссылаетесь InputReplacementImpl
вместо InputImpl
, Вы можете сгенерировать еще немного кода для выполнения переноса или просто вызвать конструктор кода, который, как ожидается, будет сгенерирован.
Я не совсем уверен, что ваш вопрос, но я надеюсь, что это проливает некоторый свет на ваши проблемы.
Немного поздно:), но одним из решений может быть использование трансформатора Byte Buddy как части процесса сборки. См., Например, https://github.com/raphw/byte-buddy/tree/master/byte-buddy-maven-plugin.
Я не знаю, соответствует ли этот совет вашим потребностям, но ваша идея будет работать в сочетании с внедрением зависимостей, например Spring.
- Создайте пользовательскую аннотацию для типов и хранения «ИСТОЧНИК», которая будет игнорироваться Spring.
- Внесите желаемое изменение в код в своем обработчике аннотаций и добавьте аннотацию Spring @Component к сгенерированному вами Java-типу и сохраните его как подтип исходного типа с именем класса, отличным от исходного.
- Когда Spring создает контекст, он загружает класс на основе вашего результата обработки вместо исходного.
Напротив, Lombok напрямую манипулирует абстрактным деревом исходного кода, что намного сложнее, чем создание исходного кода. Проблема здесь в том, что Java-Compiler-Module выставлен (Jigsaw).
Другой альтернативой может быть использование некоторого API-интерфейса байт-кода, такого как ByteBuddy, для проксирования исходного класса во время выполнения. Это также потребует внедрения зависимостей, если вы не хотите писать код для создания экземпляра класса. Здесь волшебство заключается в автосвязывании поля коллекции типов внутри класса Configuration. Дайте мне знать, если вам это интересно. Тогда я добавлю больше деталей, здесь.