Сравнение инфраструктуры динамического манипулирования байт-кодом Java

Есть несколько фреймворков для динамической генерации байт-кода, манипулирования и переплетения (BCEL, CGLIB, javassist, ASM, MPS). Я хочу узнать о них, но поскольку у меня не так много времени, чтобы знать все подробности обо всех из них, я хотел бы увидеть своего рода сравнительную таблицу, в которой указаны преимущества и недостатки одного по сравнению с другими, а также объяснение Зачем.

Здесь, в SO, я нашел много вопросов, задающих что-то подобное, и в ответах обычно говорилось: «вы можете использовать cglib или ASM», или «javassist лучше, чем cglib», или «BCEL старый и умирает» или «ASM - это лучший, потому что дает X и Y ». Эти ответы полезны, но не дают полного ответа на вопрос в том объеме, который я хочу, сравнивая их более глубоко и показывая преимущества и недостатки каждого из них.


person Victor Stafusa    schedule 06.02.2012    source источник


Ответы (3)


Анализ библиотек байт-кода

Как я могу сказать из ответов, которые вы получили здесь, и ответов на вопросы, которые вы рассмотрели, эти ответы формально не рассматривают вопрос в той явной форме, которую вы указали. Вы просили сравнения, между тем в этих ответах смутно указано, чего можно было бы хотеть, исходя из вашей цели (например, вам нужно знать байт-код? [Y / n]), или они слишком узкие.

Этот ответ представляет собой краткий анализ каждой структуры байт-кода и обеспечивает быстрое сравнение в конце.

Javassist

  • Крошечный (javassist.jar (3.21.0) составляет ~ 707 КБ / javassist-relModifier.PUBLIC22_0_cr1.zip составляет ~ 1,5 МБ)
  • Высокий (/ Низкий) -уровень
  • Простой
  • Полная функция
  • Требуется минимальное или нулевое знание формата файла класса
  • Требуется умеренное знание набора инструкций Java
  • Минимальные усилия по обучению
  • Имеет некоторые особенности однострочных / многострочных методов компиляции и вставки байт-кода.

Лично я предпочитаю Javassist просто из-за того, как быстро вы сможете его использовать, а также создавать классы и управлять ими. учебник прост и понятен. Размер файла jar составляет крошечные 707 КБ, поэтому он удобен и портативен; делает его пригодным для автономных приложений.


ASM

  • Большой (asm-6.0_ALPHA-bin.zip ~ 2,9 МБ / asm-svn-latest.tar.gz (15 октября / 2016) составляет ~ 41 МБ)
  • Низкий (/ Высокий) -уровень
  • Всесторонний
  • Полная функция
  • Порекомендуйте хорошее знание формата файла класса
  • Требуется знание набора инструкций Java
  • Умеренные учебные усилия (несколько сложно)

ASM от ObjectWeb - это очень обширная библиотека, в которой нет ничего, связанного с построением, генерацией и загрузкой классов. Фактически, у него даже есть инструменты анализа классов с предопределенными анализаторами. Считается, что это промышленный стандарт для обработки байт-кода. Это также причина, по которой я избегаю этого.

Когда я вижу примеры ASM, мне кажется, что это громоздкая задача с количеством строк, необходимых для изменения или загрузки класса. Даже некоторые параметры некоторых методов кажутся немного загадочными и неуместными для Java. С такими вещами, как ACC_PUBLIC и множеством вызовов методов с null повсюду, честно говоря, похоже, что он лучше подходит для низкоуровневого языка, такого как C. Почему бы просто не передать строковый литерал, такой как "public" или перечисление Modifier.PUBLIC? Он более дружелюбный и простой в использовании. Однако это мое мнение.

Для справки, вот руководство по ASM (4.0): https://www.javacodegeeks.com/2012/02/manipulating-java-class-files-with-asm.html


BCEL

  • Маленький (bcel-6.0-bin.zip составляет 7,3 МБ / bcel-6.0-src.zip составляет 1,4 МБ)
  • Низкий уровень
  • Адекватный
  • Выполняет свою работу
  • Требуется знание набора инструкций Java
  • Легко учить

Из того, что я видел, эта библиотека является вашей базовой библиотекой классов, которая позволяет вам делать все, что вам нужно, если вы можете сэкономить несколько месяцев или лет.

Вот учебник BCEL, который действительно разъясняет это: http://www.geekyarticles.com/2011/08/manipulating-java-class-files-with-bcel.html?m=1


cglib

  • Очень крошечный (cglib-3.2.5.jar составляет 295 КБ / исходный код)
  • Зависит от ASM
  • Высокий уровень
  • Полная функция (генерация байт-кода)
  • Требуются незначительные знания байт-кода Java или совсем их нет
  • Легко учить
  • Эзотерическая библиотека

Несмотря на то, что вы можете читать информацию из классов и что вы можете преобразовывать классы, библиотека кажется адаптированной для прокси. учебник посвящен bean-компонентам для прокси, и в нем даже упоминается, что он используется с помощью «фреймворков доступа к данным для создания динамических прокси-объектов и перехвата доступа к полям». Тем не менее, я не вижу причин, по которым вы не можете использовать его для более простых целей манипулирования байт-кодом вместо прокси.


ByteBuddy

  • Маленькая корзина / "Огромный" источник (для сравнения) (byte-buddy-dep-1.8.12.jar составляет ~ 2,72 МБ / 1.8.12 (zip) составляет 124,537 МБ (точно))
  • Зависит от ASM
  • Высокий уровень
  • Полная функция
  • Лично своеобразное имя для класса Service Pattern (ByteBuddy.class)
  • Требуется мало или совсем не требуется знаний байтового кода Java
  • Легко учить

Короче говоря, там, где BCEL не хватает, ByteBuddy в изобилии. Он использует первичный класс ByteBuddy с использованием шаблона проектирования служб. Вы создаете новый экземпляр ByteBuddy, и он представляет класс, который вы хотите изменить. Когда вы закончите свои модификации, вы можете сделать DynamicType с make().

На их веб-сайте есть полное руководство с документацией по API. Цель вроде бы для модификаций довольно высокого уровня. Когда дело доходит до методов, в официальном или стороннем учебном пособии нет ничего о создании метода с нуля, кроме делегирования метода (EDITME, если вы знаете, где это объясняется).

Их руководство можно найти здесь, на их веб-сайте. Некоторые примеры можно найти здесь.


Java Class Assistant (jCLA) ​​

У меня есть собственная библиотека байт-кода, которую я создаю, которая будет называться Java Class Assistant, или для краткости jCLA, из-за другого проекта, над которым я работаю, и из-за указанных причуд с Javassist , но я не буду ее выпускать в GitHub, пока он не будет завершен, но проект в настоящее время доступен для просмотра на GitHub и предоставления отзывов, поскольку он в настоящее время находится в альфа-версии, но все еще достаточно работоспособен, чтобы быть базовой библиотекой классов (в настоящее время работает над компиляторами; пожалуйста, помогите меня, если сможешь! Он выйдет намного раньше!).

Это будет довольно просто с возможностью чтения и записи файлов классов в файл JAR и из него, а также с возможностью компилировать и декомпилировать байт-код в исходный код и файлы классов и из них.

Общий шаблон использования упрощает работу с jCLA, , хотя может потребоваться некоторое привыкание к и, по-видимому, очень похож на ByteBuddy по стилю методов и параметрам методов для модификаций классов:

import jcla.ClassPool;
import jcla.ClassBuilder;
import jcla.ClassDefinition;
import jcla.MethodBuilder;
import jcla.FieldBuilder;

import jcla.jar.JavaArchive;

import jcla.classfile.ClassFile;

import jcla.io.ClassFileOutputStream;

public class JCLADemo {

    public static void main(String... args) {
        // get the class pool for this JVM instance
        ClassPool classes = ClassPool.getLocal();
        // get a class that is loaded in the JVM
        ClassDefinition classDefinition = classes.get("my.package.MyNumberPrinter");
        // create a class builder to modify the class
        ClassBuilder clMyNumberPrinter= new ClassBuilder(classDefinition);

        // create a new method with name printNumber
        MethodBuilder printNumber = new MethodBuilder("printNumber");
        // add access modifiers (use modifiers() for convenience)
        printNumber.modifier(Modifier.PUBLIC);
        // set return type (void)
        printNumber.returns("void");
        // add a parameter (use parameters() for convenience)
        printNumber.parameter("int", "number");
        // set the body of the method (compiled to bytecode)
        // use body(byte[]) or insert(byte[]) for bytecode
        // insert(String) also compiles to bytecode
        printNumber.body("System.out.println(\"the number is: \" + number\");");
        // add the method to the class
        // you can use method(MethodDefinition) or method(MethodBuilder)
        clMyNumberPrinter.method(printNumber.build());

        // add a field to the class
        FieldBuilder HELLO = new FieldBuilder("HELLO");
        // set the modifiers for hello; convenience method example
        HELLO.modifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL);
        // set the type of this field
        HELLO.type("java.lang.String");
        // set the actual value of this field
        // this overloaded method expects a VariableInitializer production
        HELLO.value("\"Hello from \" + getClass().getSimpleName() + \"!\"");

        // add the field to the class (same overloads as clMyNumberPrinter.method())
        clMyNumberPrinter.field(HELLO.build());

        // redefine
        classDefinition = clMyNumberPrinter.build();
        // update the class definition in the JVM's ClassPool
        // (this updates the actual JVM's loaded class)
        classes.update(classDefinition);

        // write to disk
        JavaArchive archive = new JavaArchive("myjar.jar");
        ClassFile classFile = new ClassFile(classDefinition);
        ClassFileOutputStream stream = new ClassFileOutputStream(archive);

        try {
            stream.write(classFile);
        } catch(IOException e) {
            // print to System.out
        } finally {
            stream.close();
        }
    }

}

(для вашего удобства производственная спецификация VariableInitializer. < / а>)

Как можно понять из приведенного выше фрагмента, каждый ClassDefinition неизменяем. Это делает jCLA более безопасным, поточно-ориентированным, сетевым и простым в использовании. Система вращается в первую очередь вокруг ClassDefinitions как объекта выбора для запроса информации о классе на высоком уровне, и система построена таким образом, что ClassDefinition преобразуется в целевые типы, такие как ClassBuilder и ClassFile, и обратно.

jCLA использует многоуровневую систему для данных классов. Внизу у вас есть неизменное ClassFile: структура или программное представление файла класса. Затем у вас есть неизменяемые ClassDefinition, которые конвертируются из ClassFiles во что-то менее загадочное, более управляемое и полезное для программиста, который модифицирует или считывает данные из класса, и сравнимо с информацией, доступной через java.lang.Class. Наконец, у вас есть изменяемые ClassBuilders. ClassBuilder - это то, как классы изменяются или создаются. Это позволяет вам создавать ClassDefinition прямо из построителя из его текущего состояния. Создавать новый конструктор для каждого класса не обязательно, поскольку метод reset() очистит переменные.

(Анализ этой библиотеки будет доступен, как только она будет готова к выпуску.)

Но до тех пор, на сегодняшний день:

  • Маленький (src: 227,704 КБ, точно, 02.06.2018)
  • Самодостаточный (без зависимостей, кроме поставляемой библиотеки Java)
  • Высокий уровень
  • Не требуется знания байт-кода Java или файлов классов (для API уровня 1, например, ClassBuilder, ClassDefinition и т. Д.)
  • Легко учиться (даже проще, если исходить из ByteBuddy)

Однако я все же рекомендую изучить байт-код Java. Это упростит отладку.


Сравнение

Учитывая все эти анализы (за исключением jCLA на данный момент), самой широкой структурой является ASM, самым простым в использовании является Javassist, самой базовой реализацией является BCEL, а самым высокоуровневым для генерации байт-кода и прокси является cglib.

ByteBuddy заслуживает отдельного объяснения. Его легко использовать, как Javassist, но, похоже, ему не хватает некоторых функций, которые делают Javassist отличным, таких как создание методов с нуля, поэтому вам, очевидно, придется использовать ASM для этого. Если вам нужно сделать небольшую модификацию с классами, ByteBuddy - это то, что вам нужно, но для более продвинутой модификации классов при сохранении высокого уровня абстракции лучше выбрать Javassist.

Примечание: если я пропустил библиотеку, отредактируйте этот ответ или упомяните его в комментарии.

person AMDG    schedule 26.08.2017
comment
Отличное сравнение, но вставка абзацев личного мнения о том, как должна работать библиотека, излишне отвлекает от проблемы. Кроме того, вы указываете, насколько хорош ByteBuddy и чем он лучше BCEL, но именно BCEL вы упоминаете в заключительном разделе сравнения. Вы добавили ByteBuddy позже и забыли обновить последний раздел? - person Benny Bottema; 24.04.2019
comment
@BennyBottema Да, забыл добавить в раздел сравнения. Спасибо, что указали на это. Я также удалю свое личное мнение о ByteBuddy и вместо этого, возможно, предоставлю это в качестве обратной связи непосредственно разработчикам. Это дает небольшое представление о том, как работает ByteBuddy, и как я думаю, это могло быть лучше, но это определенно неуместно. - person AMDG; 26.04.2019
comment
Поскольку прошло около года ... Я хотел бы отметить, что jCLA, скорее всего, не будет разрабатываться. Над ним не работали в течение многих лет, и я перешел от Java к стране C и ассемблера, и даже это временно, потому что я желаю для себя и каждого другого программиста действительно полный язык, который отвечает всем нашим потребностям. . Учитывая, что до настоящего времени большинство языков были ориентированы на бизнес, включая C ++ (см. yosefk.com/c+ + fqa / fqa.html), пришло время для чего-то ориентированного на программистов, без ошибочных понятий, таких как безопасность (cough Rust cough), чтобы присоединиться к игре. . - person AMDG; 11.06.2021

Если вас интересует только создание байт-кода, сравнительная таблица становится довольно простой:

Вам нужно понимать байт-код?

для javassist: нет

для всех остальных: да

Конечно, даже с javassist вы можете в какой-то момент столкнуться с концепциями байт-кода. Точно так же некоторые другие библиотеки (например, ASM) имеют более высокоуровневую поддержку API и / или инструментов, чтобы защитить вас от многих деталей байт-кода.

Что действительно отличает javassist, так это включение базового компилятора java. Это упрощает написание сложных преобразований классов: вам нужно только поместить фрагмент java в строку и использовать библиотеку для вставки его в определенные точки программы. Включенный компилятор построит эквивалентный байт-код, который затем вставляется в существующие классы.

person user1205938    schedule 13.02.2012

В первую очередь все зависит от вашей задачи. Вы хотите сгенерировать новый код или проанализировать существующий байт-код и насколько сложный анализ может вам понадобиться. Также сколько времени вы хотите потратить на изучение байт-кода Java. Вы можете разбить фреймворк байт-кода на те, которые предоставляют API высокого уровня, что позволяет вам уйти от изучения кодов операций низкого уровня и внутренних компонентов JVM (например, javaassist и CGLIB) и фреймворков низкого уровня, когда вам нужно понять JVM или использовать некоторый байт-код. инструменты генерации (ASM и BCEL). Что касается анализа, BCEL исторически развивался немного дальше, но ASM предоставляет приличную функциональность, которую легко расширять. Также обратите внимание, что ASM, вероятно, является единственной структурой, которая обеспечивает наиболее продвинутую поддержку информации STACK_MAP, необходимой для нового верификатора байт-кода, включенного по умолчанию в Java 7.

person Eugene Kuleshov    schedule 07.02.2012