В настоящее время доступно несколько библиотек для обработки байт-кода Java, таких как ASM, ApacheBCEL, Javassist и т. Д. В этой статье я расскажу о библиотеке ASM, о том, для чего она нужна, как ее использовать и как вы может решить некоторые общие проблемы, с которыми вы можете столкнуться. Это будет цикл статей, и первая будет посвящена знакомству с библиотекой и ее функциями.
Что такое ASM?
ASM - это фреймворк, который позволяет манипулировать и генерировать байт-код JVM. Он позволяет изменять существующие классы, программно генерировать новые классы и анализировать существующие классы непосредственно в их байт-кодовом (двоичном) формате. Он также имеет несколько встроенных алгоритмов для анализа цератина.
ASM API
ASM предоставляет два основных набора API: API на основе дерева и API на основе событий.
Древовидный API:
Создает древовидную структуру посещаемых классов. «ClassNode» является корнем дерева, он состоит из полей, методов, внутренних классов и другой информации в качестве дочерних элементов.
Плюсы:
- Обеспечивает полный контроль над посещаемым классом.
- Поскольку весь класс доступен в виде дерева, с ним легко манипулировать, преобразовывать и даже переписывать части дерева.
Минусы:
- Медленнее, чем API, основанный на событиях.
API на основе событий:
Этот API-интерфейс в основном основан на шаблоне посетителя и предоставляет двух основных посетителей: ClassVisitor - для посещения, анализа и преобразования существующего класса , и ClassWriter - для создание новых классов .
Плюсы:
- Быстрее, чем древовидный API.
Минусы:
- Имеет меньший контроль над текущим посещаемым классом.
- Требует неукоснительно соблюдать порядок приемов посетителей.
- Переписать класс (или его части) непросто - нужно полностью сгенерировать новый класс.
Однако из-за производительности и легкости API, основанного на событиях, он все чаще используется для генерации байт-кода и управления им. Поэтому в этой статье я также рассмотрю API, основанный на событиях.
Создание классов
Давайте посмотрим, как сгенерировать простой класс с помощью API на основе событий ASM.
Примечание. Здесь я использовал ClassWriter.COMPUTE_MAXS
в качестве флага для автора класса. Это указывает AMS автоматически вычислять максимальный размер стека и максимальное количество локальных переменных методов.
Приведенный выше код сгенерирует простой класс без методов. Чтобы записать это как файл класса, мы можем получить байты с помощьюcw.toByteArray()
и записать их в файл.
Добавление методов
Давайте добавим основной метод java для указанного выше класса. Для простоты в нашем основном методе не будет никакой логики.
Несмотря на то, что основной метод пуст, вы можете видеть, что я добавил оператор возврата mv.VisitInsn(Opcodes.RETURN)
. За ним следует mv.visitMaxs(0,0)
, который используется для установки размера массового стека для текущего метода. Поскольку мы попросили ASM вычислить это для нас во время инициализации ClassWriter, любое значение, которое мы установили через mv.visitMaxs(... , ...)
, будет проигнорировано. Однако перед вызовом mv.visitEnd()
этот метод по-прежнему необходимо вызывать.
Полный образец
Вот полный пример с java-классом, который печатает «Hello world!».
При выполнении приведенного выше примера будет создан файл класса GeneratedClass.class
. Вы можете запустить это, используя:
$ java GeneratedClass
В консоли будет напечатано Hello world!
.
В следующей статье я более подробно расскажу о байт-кодах JVM, методах ASM API, которые будут использоваться для выдачи различных байтовых кодов, и о том, как написать некоторую сложную логику на уровне байт-кода с помощью ASM.