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

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

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

Ответы [ 3 ]

20 голосов
/ 13 февраля 2012

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

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

для javassist: нет

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

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

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

11 голосов
/ 26 августа 2017

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

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

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

Javassist

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

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


ASM

  • Large ( asm-6.0_ALPHA-bin.zip is ~2.9MB / asm-svn-latest.tar.gz (15.10.2016) is ~ 41MB)
  • Низкий (/ высокий) уровень
  • Полный
  • Полнофункциональный
  • Рекомендовать опытное владение форматом файла класса
  • Требуется знание набора инструкций 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
  • Легко учиться
  • Эзотерическая библиотека

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


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 s, которые преобразуются из ClassFiles в нечто менее загадочное, более управляемое и полезное для программиста, который модифицирует или читает данные из класса, и сравнимо с информацией, доступ к которой осуществляется через java.lang.Class.Наконец, у вас есть изменяемые ClassBuilder с.ClassBuilder - это то, как классы модифицируются или создаются.Это позволяет вам создавать ClassDefinition непосредственно из компоновщика из его текущего состояния.Создание нового компоновщика для каждого класса не требуется, поскольку метод reset() очистит переменные.

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

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

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

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


Сравнение

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

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

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

8 голосов
/ 07 февраля 2012

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

...