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

时间:2012-02-06 21:21:18

标签: java-bytecode-asm javassist cglib bytecode-manipulation jvm-bytecode

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

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

3 个答案:

答案 0 :(得分:20)

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

你需要理解字节码吗?

for javassist:no

所有其他人:是的

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

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

答案 1 :(得分:10)

字节码库分析

正如我从你在这里得到的答案以及你所看到的问题中的答案中可以看出的那样,这些答案并没有以你所陈述的明确方式正式解决问题。你要求进行比较,同时这些答案模糊地说明了你的目标是什么(例如你需要知道字节码吗?[y / n]),还是太窄了。

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

Javassist

  • Tiny(javassist.jar (3.21.0) ~707KB / javassist-rel_3_22_0_cr1.zip约为1.5MB)
  • 高(/低)级
  • 直截了当
  • 特征的完整
  • 需要最少到没有类文件格式的知识
  • 需要中等的Java指令集知识
  • 最少的学习努力
  • 在单行/多行编译插入字节码方法中有一些怪癖

我个人更喜欢Javassist,因为你可以多快地使用它并使用它来构建和操作类。 tutorial简单明了,易于理解。 jar文件是一个小的707KB,所以它很好,便携;使其适用于独立应用程序。

ASM

ObjectWeb的ASM是一个非常全面的库,它与构建,生成和加载类没有任何关系。实际上,它甚至还具有带预定义分析器的类分析工具。它被认为是字节码操作的行业标准。这也是我避开它的原因。

当我看到ASM的例子时,它似乎是一个繁琐的任务,它具有修改或加载类所需的行数。甚至一些方法的一些参数似乎有点神秘而且不适合Java。对于像ACC_PUBLIC这样的东西,以及到处都有null的大量方法调用,老实说看起来它更适合像C这样的低级语言。为什么不简单地只传递一个像String这样的字符串文字#34; public"或enum Modifier.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 / source code
  • 取决于ASM
  • 高级别
  • 功能完整(字节代)
  • 很少或不需要Java字节码知识
  • 易于学习
  • 神秘图书馆

尽管您可以从类中读取信息,并且您可以转换类,但该库似乎适合于代理。 tutorial完全是关于代理的bean,它甚至提到它被数据访问框架用于生成动态代理对象和拦截字段访问。"尽管如此,我认为你没有理由不能使用它来实现字节码操作的更简单目的而不是代理。

ByteBuddy

  • 小垃圾箱/ "巨大" 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文档的完整教程。目的似乎是进行相当高级别的修改。当涉及到方法时,除了委派方法之外,在官方教程或任何第三方教程中似乎没有任何关于从头开始创建方法的内容( EDITME ,如果你知道这在哪里解释)。

他们的教程可以找到here on their website。可以找到一些示例here

Java Class Assistant (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 production specification for your convenience.

从上面的代码段可能暗示,每个ClassDefinition都是不可变的。这使jCLA更安全,线程安全,网络安全且易于使用。系统主要围绕ClassDefinitions作为以高级方式查询有关类的信息的首选对象,系统的构建方式使得ClassDefinition与目标类型(如ClassBuilder和ClassFile)进行转换。

jCLA对类数据使用分层系统。在底部,您有不可变的ClassFile:类文件的结构或软件表示。然后你有不可变的ClassDefinition,它们从ClassFiles转换成一些不那么神秘,更易于管理和有用的程序员正在修改或读取类中的数据,并且与通过java.lang.Class访问的信息相当。最后,你有可变的ClassBuilder s。 ClassBuilder是修改或创建类的方法。它允许您可以从构建器的当前状态直接创建ClassDefinition。不需要为每个类创建新的构建器,因为reset()方法将清除变量。

(一旦准备好发布,就可以对该库进行分析。)

但直到那时,截至今天:

  • 小(src:227.704 KB,确切地说,6/2/2018)
  • 自给自足(除Java以外没有依赖项库)
  • 高级别
  • 不需要了解java字节码或类文件(对于第1层API,例如ClassBuilder,ClassDefinition等)。
  • 易于学习(如果来自ByteBuddy,则更容易)

我仍然建议学习java字节码。它将使调试更容易。

比较

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

ByteBuddy值得自己解释。它很容易像Javassist一样使用,但似乎缺少一些使Javassist很棒的功能,比如从头开始创建方法,因此你需要使用ASM。如果你需要对类进行一些轻量级修改,ByteBuddy是可行的方法,但是为了在保持高级抽象的同时对类进行更高级的修改,Javassist是一个更好的选择。

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

答案 2 :(得分:8)

首先,这一切都取决于你的任务。您想生成新代码还是分析现有字节码以及您可能需要的复杂分析。您还需要投入多少时间来学习Java字节码。您可以将字节码框架分解为提供高级API的框架,允许您在需要了解JVM或使用某些字节码时远离学习低级操作码和JVM内部(例如,javaassist和CGLIB)和低级框架生成工具(ASM和BCEL)。对于analyzis BCEL历史上进化了一些,但ASM提供了一个容易扩展的体面功能。另请注意,ASM可能是唯一一个为Java 7中默认启用的新字节码验证程序所需的STACK_MAP信息提供最高级支持的框架。