字节码库分析
正如我从你在这里得到的答案和你所看到的问题中可以看出的那样,这些答案并没有以你所说的明确方式正式解决这个问题。您要求进行比较,同时这些答案模糊地说明了根据您的目标可能想要什么(例如,您需要知道字节码吗?[y/n]),或者过于狭窄。
这个答案是对每个字节码框架的简短分析,并在最后提供了一个快速比较。
我个人更喜欢 Javassist,因为您可以快速使用它并使用它构建和操作类。本教程简单易懂。jar 文件很小,只有 707KB,所以它很漂亮且可移植;使其适用于独立应用程序。
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 教程:http ://www.geekyarticles.com/2011/08/manipulating-java-class-files-with-bcel.html?m=1
尽管您可以从类中读取信息,并且可以转换类,但该库似乎是为代理量身定制的。本教程是关于代理 bean 的,它甚至提到它被“数据访问框架用来生成动态代理对象和拦截字段访问”。尽管如此,我认为没有理由不能将它用于更简单的字节码操作而不是代理目的。
长话短说,在缺乏 BCEL 的地方,ByteBuddy 是丰富的。它使用服务设计模式使用一个名为 ByteBuddy 的主要类。您创建一个 ByteBuddy 的新实例,这表示您要修改的类。完成修改后,您可以DynamicType
使用make()
.
在他们的网站上有一个完整的 API 文档教程。目的似乎是进行相当高级的修改。在方法方面,官方教程或任何 3rd 方教程中似乎没有任何关于从头开始创建方法的内容,除了委派方法(如果你知道在哪里解释的话,请编辑 EDITME )。
他们的教程可以在他们的网站上找到。一些例子可以在这里找到。
我有自己正在构建的字节码库,将被称为 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 ClassBuilder
s。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 是更好的选择。
注意:如果我错过了图书馆,请编辑此答案或在评论中提及。