Через два месяца после первого коммита в октябре 2022 года Питер Верхас, старший архитектор EPAM Systems, выпустил версию 2.0.0 SourceBuddy, новой утилиты, которая компилирует динамически исходный код Java, заданный в строке или файле, в файл класса.
Утилит SourceBuddy требует Java 17 и представляет собой упрощенный фасад для компилятора javac, который обеспечивает ту же функциональность.
Версия 2.0.0 поддерживает комбинацию скрытых и нескрытых классов во время компиляции и выполнения. Кроме того, был упрощен API, включая критические изменения, такие как изменение метода loadHidden() на метод hidden(), поэтому и выпущен новый основной релиз. Полный обзор изменений для каждой версии доступен в документации по выпускам на GitHub.
SourceBuddy можно использовать после добавления следующей зависимости Maven:
<dependency> <groupId>com.javax0.sourcebuddy</groupId> <artifactId>SourceBuddy</artifactId> <version>2.0.0</version> </dependency>
В качестве альтернативы можно использовать следующую зависимость Gradle:
implementation 'com.javax0.sourcebuddy:SourceBuddy:2.0.0'
Чтобы продемонстрировать SourceBuddy, рассмотрим следующий пример интерфейса, который будет использоваться динамически создаваемым кодом:
package com.app; public interface PrintInterface { void print(); }
Простой API способен компилировать один класс за раз, используя статический метод com.javax0.sourcebuddy.Compiler.compile(). Вот пример для компиляции нового класса, реализующего ранее упомянутый интерфейс PrintInterface:
String source = """ package com.app; public class CustomClass implements PrintInterface { @Override public void print() { System.out.println("Hello world!"); } }"""; Class<?> clazz = Compiler.compile(source); PrintInterface customClass = (PrintInterface) clazz.getConstructor().newInstance(); customClass.print();
Fluent API предлагает функции для решения более сложных задач, таких как компиляция нескольких файлов с помощью статического метода Compiler.java():
Compiler.java().from(source).compile().load().newInstance(PrintInterface.class);
При желании можно указать двоичное имя класса, хотя SourceBuddy уже определит имя, когда это возможно:
.from("com.app", source)
Для нескольких исходных файлов метод from() может быть вызван несколько раз, или все исходные файлы в определенном каталоге могут быть загружены сразу:
.from(Paths.get("src/main/java/sourcefiles"))
При желании метод hidden() может быть использован для создания скрытого класса, который не может быть использован другими классами напрямую, только посредством рефлексии с использованием объекта Class, возвращаемого SourceBuddy.
Метод compile() генерирует байт-коды для исходных файлов Java, но пока не загружает их в память.
final var byteCodes = Compiler.java() .from("com.app", source) .compile();
При желании байт-коды могут быть сохранены на локальном диске:
byteCodes.saveTo(Paths.get("./target/generated_classes"));
В качестве альтернативы можно использовать метод stream(), который возвращает поток байтовых массивов и может использоваться для получения такой информации, как двоичное имя:
byteCodes.stream().forEach( bytecode -> System.out.println(Compiler.getBinaryName(bytecode)));
Метод byteCodes.load() загружает классы и преобразует байт-код в объекты типа Class:
final var loadedClasses = compiled.load();
Доступ к классу возможен путем приведения к суперклассу или интерфейсу, который реализует класс, или с помощью API рефлексии. Вот пример как получить доступ к классу CustomClass:
Class<?> customClass = loadedClasses.get("com.app.CustomClass");
В качестве альтернативы для создания экземпляра класса можно использовать метод newInstance():
Object customClassInstance = loadedClasses.newInstance("com.app.CustomClass");
Поток классов может быть использован для получения дополнительной информации о классах:
loadedClasses.stream().forEach( clazz -> System.out.println(clazz.getSimpleName()));
Более подробную информацию о SourceBuddy можно найти в подробных пояснениях в файле README на GitHub.
ссылка на оригинал статьи https://habr.com/ru/post/711922/
Добавить комментарий