40

有一些用于动态字节码生成、操作和编织的框架(BCEL、CGLIB、javassist、ASM、MPS)。我想了解它们,但是由于我没有太多时间了解它们的所有细节,所以我希望看到一种比较图表,说明一个与另一个的优缺点以及对它们的解释为什么。

在 SO 中,我发现很多问题都在问类似的问题,答案通常是“你可以使用 cglib 或 ASM”,或者“javassist 比 cglib 好”,或者“BCEL 已经老了,快要死了”或者“ASM 是最好的,因为它给出了 X 和 Y”。这些答案很有用,但并没有完全回答我想要的范围内的问题,更深入地比较它们并给出每个的优缺点。

4

3 回答 3

32

字节码库分析

正如我从你在这里得到的答案和你所看到的问题中可以看出的那样,这些答案并没有以你所说的明确方式正式解决这个问题。您要求进行比较,同时这些答案模糊地说明了根据您的目标可能想要什么(例如,您需要知道字节码吗?[y/n]),或者过于狭窄。

这个答案是对每个字节码框架的简短分析,并在最后提供了一个快速比较。

Javassist

  • 很小(javassist.jar (3.21.0)约为 707KB / javassist-rel_3_22_0_cr1.zip约为 1.5MB)
  • 高(/低)级
  • 直截了当
  • 功能齐全
  • 需要最少或不需要类文件格式知识
  • 需要适度的 Java 指令集知识
  • 最少的学习努力
  • 在单行/多行编译和插入字节码方法中有一些怪癖

我个人更喜欢 Javassist,因为您可以快速使用它并使用它构建和操作类。本教程简单易懂。jar 文件很小,只有 707KB,所以它很漂亮且可移植;使其适用于独立应用程序。


ASM

ObjectWeb 的 ASM 是一个非常全面的库,它缺少与构建、生成和加载类相关的任何内容。事实上,它甚至还有带有预定义分析器的类分析工具。据说它是字节码操作的行业标准。这也是我远离它的原因。

当我看到 ASM 的示例时,它似乎是一项繁琐的任务,需要修改或加载一个类所需的行数。甚至某些方法的某些参数对于 Java 来说似乎有点神秘和格格不入。有了ACC_PUBLIC, 以及null到处都有大量方法调用,老实说,它看起来确实更适合像 C 这样的低级语言。为什么不简单地传递一个像“public”这样的字符串文字,或者一个 enumModifier.PUBLIC呢?它更友好且易于使用。然而,这是我的看法。

作为参考,这里有一个 ASM (4.0) 教程:https ://www.javacodegeeks.com/2012/02/manipulating-java-class-files-with-asm.html


BCEL

据我所知,这个库是你的基本类库,它可以让你做你需要做的一切——如果你能腾出几个月或几年的时间。

这是一个真正说明它的 BCEL 教程:http ://www.geekyarticles.com/2011/08/manipulating-java-class-files-with-bcel.html?m=1


cglib

  • 非常小(cglib-3.2.5.jar是 295KB/源代码
  • 取决于 ASM
  • 高水平
  • 功能完备(字节码生成)
  • 需要很少或不需要 Java 字节码知识
  • 简单易学
  • 深奥图书馆

尽管您可以从类中读取信息,并且可以转换类,但该库似乎是为代理量身定制的。本教程是关于代理 bean 的,它甚至提到它被“数据访问框架用来生成动态代理对象和拦截字段访问”。尽管如此,我认为没有理由不能将它用于更简单的字节码操作而不是代理目的。


字节好友

  • Small bin/ “Huge” src(相比之下)byte-buddy-dep-1.8.12.jar约为 2.72 MB / 1.8.12(zip)为 124.537 MB(精确))
  • 取决于 ASM
  • 高水平
  • 功能齐全
  • 就个人而言,服务模式类 (ByteBuddy.class) 的特殊名称
  • 需要很少或不需要 Java 字节码知识
  • 简单易学

长话短说,在缺乏 BCEL 的地方,ByteBuddy 是丰富的。它使用服务设计模式使用一个名为 ByteBuddy 的主要类。您创建一个 ByteBuddy 的新实例,这表示您要修改的类。完成修改后,您可以DynamicType使用make().

在他们的网站上有一个完整的 API 文档教程。目的似乎是进行相当高级的修改。在方法方面,官方教程或任何 3rd 方教程中似乎没有任何关于从头开始创建方法的内容,除了委派方法(如果你知道在哪里解释的话,请编辑 EDITME )。

他们的教程可以在他们的网站上找到。一些例子可以在这里找到。


Java 类助手 (jCLA)

我有自己正在构建的字节码库,将被称为 Java 类助手,简称 jCLA,因为我正在从事另一个项目,并且因为 Javassist 的上述怪癖,但我不会将它发布到 GitHub,直到它已经完成,但该项目目前可以在 GitHub 上浏览并提供反馈,因为它目前处于 alpha 阶段,但仍然可以作为一个基本的类库(目前正在编译器上工作;如果可以的话,请帮助我!它将很快就会被释放!)。

它将非常简单,能够从 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 对类数据使用分层系统。在底部,您有 immutable ClassFile:类文件的结构或软件表示。然后你有不可变ClassDefinition的 s ,它们从 ClassFiles 转换为不那么神秘、更易于管理和对正在修改或读取类数据的程序员有用的东西,并且与通过java.lang.Class. 最后,你有 mutable ClassBuilders。ClassBuilder 是修改或创建类的方式。它允许您ClassDefinition直接从构建器的当前状态创建一个。不需要为每个类创建一个新的构建器,因为该reset()方法会清除变量。

(这个库的分析将在它准备好发布后立即可用。)

但在那之前,截至今天:

  • 小(src:227.704 KB 精确,2018 年 6 月 2 日)
  • 自给自足(除了 Java 自带的库外没有依赖)
  • 高水平
  • 不需要 Java 字节码或类文件知识(对于第 1 层 API,例如 ClassBuilder、ClassDefinition 等)
  • 易于学习(如果来自 ByteBuddy 则更容易)

不过,我仍然建议学习 Java 字节码。这将使调试更容易。


比较

考虑到所有这些分析(目前不包括 jCLA),最广泛的框架是 ASM,最容易使用的是 Javassist,最基本的实现是 BCEL,而用于字节码生成和代理的最高级别是 cglib。

ByteBuddy 值得自己解释。它像 Javassist 一样易于使用,但似乎缺少一些使 Javassist 出色的功能,例如从头开始创建方法,因此您显然需要使用 ASM。如果需要对类进行一些轻量级的修改,ByteBuddy 是不二之选,但对于更高级的类修改同时保持高度抽象,Javassist 是更好的选择。

注意:如果我错过了图书馆,请编辑此答案或在评论中提及。

于 2017-08-26T03:24:53.253 回答
22

如果您对字节码生成的兴趣只是为了使用它,那么对比图就变得相当简单:

你需要了解字节码吗?

对于 javassist:没有

对于所有其他人:是的

当然,即使使用 javassist,您也可能在某些时候遇到字节码概念。同样,其他一些库(例如 ASM)具有更高级别的 api 和/或工具支持,可以保护您免受许多字节码细节的影响。

然而,真正区分 javassist 的是包含一个基本的 java 编译器。这使得编写复杂的类转换变得非常容易:您只需将 java 片段放入 String 并使用库将其插入程序中的特定点。包含的编译器将构建等效的字节码,然后将其插入现有的类中。

于 2012-02-13T04:06:20.327 回答
7

首先,这完全取决于您的任务。您想生成新代码还是分析现有字节码以及您可能需要多复杂的分析。还有你想投入多少时间来学习 Java 字节码。您可以将字节码框架分解为提供高级 API 的框架,当您需要了解 JVM 或使用一些字节码时,这使您可以摆脱学习低级操作码和 JVM 内部(例如 javaassist 和 CGLIB)和低级框架生成工具(ASM 和 BCEL)。对于分析而言,BCEL 在历史上发展得更多,但 ASM 提供了一个不错的功能,易于扩展。另请注意,ASM 可能是唯一为 Java 7 中默认启用的新字节码验证器所需的 STACK_MAP 信息提供最高级支持的框架。

于 2012-02-07T18:15:28.417 回答