字节码库分析
从您在这里得到的答案和您查看的问题中的答案来看,这些答案并没有正式地以您明确陈述的方式回答问题。您要求进行比较,而这些答案模糊地说明了基于您的目标可能需要什么(例如,您是否需要了解字节码?[是/否]),或者过于狭窄。
本答案对每个字节码框架进行了简短的分析,并在结尾提供了快速比较。
我个人更喜欢Javassist,因为你可以很快地使用它来构建和操纵类。教程直截了当,易于跟随。jar文件只有707KB,非常小巧,便于携带;适用于独立应用程序。
ObjectWeb 的 ASM 是一个非常全面的库,与构建、生成和加载类有关的一切都不缺。事实上,它甚至具有预定义分析器的类分析工具。据说它是字节码操作的行业标准。这也是我远离它的原因。
当我看到 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教程:http://www.geekyarticles.com/2011/08/manipulating-java-class-files-with-bcel.html?m=1
尽管您可以从类中读取信息,也可以转换类,但该库似乎专门针对代理。 tutorial 关于代理的所有内容都是关于代理的bean,它甚至提到了它被“数据访问框架用于生成动态代理对象和拦截字段访问”。尽管如此,我仍然看不出为什么您不能将其用于更简单的目的,即代替代理进行字节码操作。
长话短说,在BCEL缺乏的地方,ByteBuddy丰富多彩。它使用一个名为ByteBuddy的主类,使用服务设计模式。您可以创建一个新的ByteBuddy实例,这代表了您想要修改的类。当您完成修改后,就可以使用make()
创建一个DynamicType
。
在他们的网站上,有一个包含API文档的完整教程。目的似乎是进行相当高级的修改。关于方法,官方教程或任何第三方教程中似乎没有关于从头创建方法的内容,除了委托方法(如果您知道这是在哪里解释的,请编辑此处)。
他们的教程可以在
他们的网站上找到。一些示例可以在
这里找到。
我正在构建自己的字节码库,它将被称为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) {
ClassPool classes = ClassPool.getLocal();
ClassDefinition classDefinition = classes.get("my.package.MyNumberPrinter");
ClassBuilder clMyNumberPrinter= new ClassBuilder(classDefinition);
MethodBuilder printNumber = new MethodBuilder("printNumber");
printNumber.modifier(Modifier.PUBLIC);
printNumber.returns("void");
printNumber.parameter("int", "number");
printNumber.body("System.out.println(\"the number is: \" + number\");");
clMyNumberPrinter.method(printNumber.build());
FieldBuilder HELLO = new FieldBuilder("HELLO");
HELLO.modifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL);
HELLO.type("java.lang.String");
HELLO.value("\"Hello from \" + getClass().getSimpleName() + \"!\"");
clMyNumberPrinter.field(HELLO.build());
classDefinition = clMyNumberPrinter.build();
classes.update(classDefinition);
JavaArchive archive = new JavaArchive("myjar.jar");
ClassFile classFile = new ClassFile(classDefinition);
ClassFileOutputStream stream = new ClassFileOutputStream(archive);
try {
stream.write(classFile);
} catch(IOException e) {
} finally {
stream.close();
}
}
}
(变量初始化器生产规范供您参考。)
从上面的片段可以看出,每个ClassDefinition
都是不可变的。这使得jCLA更安全、线程安全、网络安全和易于使用。该系统主要围绕ClassDefinitions作为高级方式查询有关类信息的对象,并且该系统是以这样一种方式构建的,即ClassDefinition被转换为目标类型(如ClassBuilder和ClassFile)。
jCLA使用分层系统来处理类数据。在底部,您有不可变的ClassFile
:一个类文件的结构或软件表示。然后你有不可变的ClassDefinition
,它们被转换成比较不加密和更易于管理和使用的东西,对于修改或读取类中的数据的程序员来说是可比较的,类似于通过java.lang.Class
访问的信息。最后,您有可变的ClassBuilder
。ClassBuilder是如何修改或创建类的。它允许您直接从当前状态的构建器创建ClassDefinition
。不需要为每个类创建新的构建器,因为reset()
方法会清除变量。
(该库的分析将在发布时提供。)
但在此之前,截至今天:
- 小巧 (源文件: 227.704 KB 精确, 6/2/2018)
- 自给自足 (除了Java的已提供库,无需其他依赖)
- 高级别的
- 不需要java bytecode或class文件知识 (对于一级API如ClassBuilder, ClassDefinition等)
- 易学习 (如果从ByteBuddy转来则更容易)
我仍然建议学习java bytecode。这将使调试更容易。
比较
考虑所有这些分析(暂时不包括jCLA),最广泛的框架是ASM,最易于使用的是Javassist,最基本的实现是BCEL,而用于字节码生成和代理的最高级别是cglib。
ByteBuddy值得单独解释。它像Javassist一样易于使用,但似乎缺少一些使Javassist变得出色的功能,例如从头开始创建方法,因此显然需要使用ASM。如果您需要对类进行轻量级修改,则应选择ByteBuddy,但是如果需要在保持高度抽象的同时进行更高级别的类修改,则应选择Javassist。
注意:如果我错过了某个库,请编辑此答案或在评论中提到它。