Подтвердить что ты не робот

Сравнение динамических схем манипуляции с байткодом Java

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

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

4b9b3361

Ответ 1

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

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

для javassist: no

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

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

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

Ответ 2

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

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

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

Javassist

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

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


КАК М

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

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

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

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


BCEL

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

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

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


CGLIB

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

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


ByteBuddy

  • Small bin/"Huge" src (для сравнения) (byte-buddy-dep-1.8.12.jar составляет ~ 2,72 МБ /1,8,12 (zip) - 124,537 МБ (точно))
  • Зависит от ASM
  • Высокий -level
  • Функция-полной
  • Лично, своеобразное имя для класса Service Pattern (ByteBuddy.class)
  • Требуется мало или нет знаний о байт-коде Java
  • Легко обучаема

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

На их сайте есть полное руководство с документацией по API. Похоже, что цель - довольно высокие модификации -level. Когда дело доходит до методов, в официальном учебнике или стороннем учебнике, похоже, нет ничего о создании метода с нуля, кроме делегирования метода (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 ClassPool
        // (this updates the actual JVM 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 как объекта выбора для запроса информации о классе с высокой степенью -level, и система построена таким образом, что ClassDefinition преобразуется в целевые типы и из них, такие как ClassBuilder и ClassFile.

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

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

Но до тех пор, как сегодня

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

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


сравнение

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

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

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

Ответ 3

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