动态 Java 字节码操作框架比较

发布于 2025-01-02 20:10:53 字数 300 浏览 1 评论 0原文

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

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

There are some frameworks out there for dynamic bytecode generation, manipulation and weaving (BCEL, CGLIB, javassist, ASM, MPS). I want to learn about them, but since I don't have much time to know all the details about all of them, I would like to see a sort of comparison chart saying the advantages and disadvantages of one versus the others and an explanation of why.

Here in SO, I found a lot of questions asking something similar, and the answers normally said "you can use cglib or ASM", or "javassist is better than cglib", or "BCEL is old and is dying" or "ASM is the best because it gives X and Y". These answers are useful, but does not fully answer the question in the scope that I want, comparing them more deeply and giving the advantages and disadvantages of each one.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

秉烛思 2025-01-09 20:10:53

字节码库分析

正如我从您在此处获得的答案以及您所查看的问题中的答案可以看出的那样,这些答案并未以您所说的明确方式正式解决该问题。您要求进行比较,同时这些答案已经模糊地说明了根据您的目标可能想要什么(例如,您需要知道字节码吗?[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”这样的字符串文字,还是一个枚举Modifier.PUBLIC?它更加友好且易于使用。然而,这是我的意见。

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


BCEL

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

这里有一个 BCEL 教程,真正说明了这一点:http://www.geekyarticles.com/2011/08/manipulated-java-class-files-with-bcel.html?m=1


cglib

  • 非常小(cglib-3.2.5.jar 为 295KB/源代码)
  • 取决于 ASM
  • 高级
  • 功能完整(字节码生成)
  • 很少或没有所需的 Java 字节码知识
  • 易于学习
  • 深奥的库

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


ByteBuddy

  • 小 bin/“巨大”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 的一个新实例,它代表您要修改的类。完成修改后,您可以使用 make() 创建一个 DynamicType

他们的网站上有完整的教程和 API 文档。目的似乎是为了进行相当高级的修改。当谈到方法时,除了委托方法之外,官方教程或任何第三方教程中似乎没有任何关于从头开始创建方法的内容(编辑,如果你知道这个方法在哪里)已解释)。

您可以在其网站上找到他们的教程。可以在此处找到一些示例。


Java 类助手 (jCLA)

我正在构建自己的字节码库,它将称为 Java 类Assistant,简称 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 对类数据使用分层系统。在底部,您有不可变的 ClassFile:类文件的结构或软件表示。然后,您就拥有了不可变的 ClassDefinition,它们从 ClassFile 转换为对修改或从类中读取数据的程序员来说更不那么神秘、更易于管理和有用的东西,并且与通过 访问的信息相当java.lang.Class。最后,您有可变的 ClassBuilder。 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 是更好的选择。

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

Analysis of bytecode libraries

As I can tell from the answers you got here and ones in the questions that you have looked at, these answers do not formally address the question in the explicit manner you have stated. You asked for a comparison, meanwhile these answers have vaguely stated what one might want based on what your target is (e.g. Do you need to know bytecode? [y/n]), or are too narrow.

This answer is a short analysis of each bytecode framework, and provides a quick comparison at the end.

Javassist

  • Tiny (javassist.jar (3.21.0) is ~707KB / javassist-rel_3_22_0_cr1.zip is ~1.5MB)
  • High(/Low)-level
  • Straightforward
  • Feature-complete
  • Requires minimal to no class file format knowledge
  • Requires moderate Java instruction set knowledge
  • Minimal learning effort
  • Has some quirks in the single-line/multi-line compile-and-insert-bytecode methods

I personally prefer Javassist simply because of how quickly you can get to using it and building and manipulating classes with it. The tutorial is straightforward and easy to follow. The jar file is a tiny 707KB, so it is nice and portable; makes it suitable for standalone applications.


ASM

ASM by ObjectWeb is a very comprehensive library which lacks nothing related to building, generating, and loading classes. In fact, it even has class analysis tools with predefined analyzers. It is said to be the industry standard for bytecode manipulation. It is also the reason why I steer clear away from it.

When I see examples of ASM, it seems like a cumbersome beast of a task with the number of lines it takes to modify or load a class. Even some of the parameters to some methods seem a bit cryptic and out of place for Java. With things like ACC_PUBLIC, and plenty of method calls with null everywhere, it honestly does look like it is better suited for a low-level language like C. Why not simply just pass a String literal like "public", or an enum Modifier.PUBLIC? It's more friendly and easy to use. That is my opinion, however.

For reference, here is an ASM (4.0) tutorial: https://www.javacodegeeks.com/2012/02/manipulating-java-class-files-with-asm.html


BCEL

From what I have seen, this library is your basic class library that lets you do everything you need to—if you can spare a few months or years.

Here is a BCEL tutorial that really spells it out: http://www.geekyarticles.com/2011/08/manipulating-java-class-files-with-bcel.html?m=1


cglib

  • Very tiny (cglib-3.2.5.jar is 295KB/source code)
  • Depends on ASM
  • High-level
  • Feature-complete (Bytecode Generation)
  • Little or no Java bytecode knowledge needed
  • Easy to learn
  • Esoteric Library

Despite the fact that you can read information from classes, and that you can transform classes, the library seems tailored to proxies. The tutorial is all about beans for the proxies, and it even mentions it is used by "data access frameworks to generate dynamic proxy objects and intercept field access." Still, I see no reason why you can't use it for the more simple purpose of bytecode manipulation instead of proxies.


ByteBuddy

  • Small bin/"Huge" src (by comparison) (byte-buddy-dep-1.8.12.jar is ~2.72 MB / 1.8.12 (zip) is 124.537 MB (exact))
  • Depends on ASM
  • High-level
  • Feature-complete
  • Personally, a peculiar name for a Service Pattern class (ByteBuddy.class)
  • Little or no Java byte code knowledge needed
  • Easy to learn

Long story short, where BCEL is lacking, ByteBuddy is abundant. It uses a primary class called ByteBuddy using the Service Design Pattern. You create a new instance of ByteBuddy, and this represents a class that you want to modify. When you are done with your modifications, you can then make a DynamicType with make().

On their website is a full tutorial with API documentation. The purpose seems to be for rather high-level modifications. When it comes to methods, there does not appear to be anything in the official tutorial, or any 3rd party tutorial, about creating a method from scratch, apart from delegating a method (EDITME if you know where this is explained).

Their tutorial can be found here on their website. Some examples can be found here.


Java Class Assistant (jCLA)

I have my own bytecode library that I am building, which will be called Java Class Assistant, or jCLA for short, because of another project I am working on and because of said quirks with Javassist, but I will not be releasing it to GitHub until it is finished but the project is currently available to browse on GitHub and give feedback on as it is currently in alpha, but still workable enough to be a basic class library (currently working on the compilers; please help me if you can! It will be released a lot sooner!).

It will be quite bare bones with the ability to read and write class files to and from a JAR file, as well as the ability to compile and decompile bytecode to and from source code and class files.

The overall usage pattern makes it rather easy to work with jCLA, though it may take some getting used to and is apparently quite similar to ByteBuddy in its style of methods and method parameters for class modifications:

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 production specification for your convenience.)

As may be implied from the above snippet, each ClassDefinition is immutable. This makes jCLA more secure, thread-safe, network-safe, and easy to use. The system revolves primarily around ClassDefinitions as the object of choice for querying information about a class in a high-level manner, and the system is built in such a way that ClassDefinition is converted to and from target types such as ClassBuilder and ClassFile.

jCLA uses a tiered system for class data. At the bottom, you have the immutable ClassFile: a struct or software representation of a class file. Then you have immutable ClassDefinitions which are converted from ClassFiles into something less cryptic and more manageable and useful to the programmer who is modifying or reading data from the class, and is comparable to information accessed through java.lang.Class. Finally, you have mutable ClassBuilders. The ClassBuilder is how classes are modified or created. It allows that you can create a ClassDefinition directly from the builder from its current state. Creating a new builder for each class is not necessary as the reset() method will clear the variables.

(Analysis of this library will be available as soon as it is ready for release.)

But until then, as of today:

  • Small (src: 227.704 KB exact, 6/2/2018)
  • Self-sufficient (no dependencies except Java's shipped library)
  • High-level
  • No required knowledge of java bytecode or class files (for tier 1 API, e.g. ClassBuilder, ClassDefinition, etc.)
  • Easy to learn (even easier if coming from ByteBuddy)

I still recommend learning about java bytecode however. It will make debugging easier.


Comparison

Considering all of these analyses (excluding jCLA for now), the broadest framework is ASM, the easiest to use is Javassist, the most basic implementation is BCEL, and the most high-level for bytecode generation and proxies is cglib.

ByteBuddy deserves its own explanation. It is easy to use like Javassist, but appears to be lacking some of the features that make Javassist great, such as method creation from scratch, so you would need to use ASM for that apparently. If you need to do some lightweight modification with classes, ByteBuddy is the way to go, but for more advanced modification of classes while maintaining a high level of abstraction, Javassist is a better choice.

Note: if I missed a Library, please edit this answer or mention it in a comment.

像你 2025-01-09 20:10:53

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

您需要了解字节码吗?

对于 javassist :否

对于所有其他人:是

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

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

If your interest in bytecode generation is only to use it, the comparison chart becomes rather simple :

Do you need to understand bytecode?

for javassist : no

for all others : yes

Of course, even with javassist you may be confronted with bytecode concepts at some point. Likewise, some of the other libraries (such as ASM) have a more high-level api and/or tool support to shield you from many of the bytecode details.

What really distinguishes javassist though, is the inclusion of a basic java compiler. This makes it very easy to write complex class transformations : you only have to put a java fragment in a String and use the library to insert it at specific points in the program. The included compiler will build the equivalent bytecode, which is then inserted into the existing classes.

萌化 2025-01-09 20:10:53

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

First of all it all depends on your task. Do you want to generate the new code or analyze existing bytecode and how complex analysis you may need. Also how much time you want to invest into learning Java bytecode. You can break down bytecode framework into ones that provide a high level API, that allows you to get away from learning low level opcodes and JVM internals (e.g, javaassist and CGLIB) and low level frameworks when you need to understand JVM or use some bytecode generation tools (ASM and BCEL). For analyzis BCEL historically evolved a bit more, but ASM provides a decent functionality that is easy to extend. Also note, ASM is probably the only framework that provides the most advanced support for STACK_MAP information required by the new bytecode verifier enabled by default in Java 7.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文