Как стать автором
Обновить

SourceBuddy динамически компилирует исходный код Java

Время на прочтение 3 мин
Количество просмотров 2.9K
Автор оригинала: Johan Janssen

Через два месяца после первого коммита в октябре 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.

Теги:
Хабы:
0
Комментарии 13
Комментарии Комментарии 13

Публикации

Истории

Работа

Java разработчик
359 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн