В настоящее время доступно несколько библиотек для обработки байт-кода 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.