Javassist之字节码级的API

作者: bdqfork | 来源:发表于2018-07-03 20:41 被阅读11次

    Javassist提供了低级API来直接编辑类文件。为了使用这些API,你需要详细了解Java字节码和类文件的格式,这样你就可以通过这些API对类文件进行各种修改。

    如果你想要产生一个简单的类文件,javassist.bytecode.ClassFileWriter可能提供了最好的API。它提供了比javassist.bytecode.ClassFile更快的速度,尽管这个API更小一些。

    1. 获取ClassFile对象

    javassist.bytecode.ClassFile对象表示一个类文件。为了获取这个对象,应该调用CtClass的getClassFile()。

    除此之外,你可以直接从类文件构造一个javassit.bytecode.ClassFile。例如,

    BufferedInputStream fin
        = new BufferedInputStream(new FileInputStream("Point.class"));
    ClassFile cf = new ClassFile(new DataInputStream(fin));
    

    这些代码片段从Point.class创建了一个ClassFile对象。

    一个ClassFile对象可以被写回类文件。ClassFile的write()将类文件的内容写入DataOutputStream。

    2. 添加和移除成员

    ClassFile提供了addField()和addMethod()来添加字段或方法(注意构造函数在字节码级别被当作普通的方法)。它提供addAttribute()来添加一个属性到类文件。

    注意FieldInfo,MethodInfo,AttributeInfo对象包括一个到ConstPool(常量池表)引用。ConstPool对象必须是ClassFile对象和FieldInfo(或MethodInfo等)的公用对象,该对象被添加到这些ClassFile对象中。换句话说,FieldInfo(或MethodInfo等)对象禁止在不同的ClassFile对象中共享。

    为了从ClassFile对象中移除字段或方法,你必须先获取一个包含了类所有字段的java.util.List对象。getFields()和getMethods()返回一个列表。字段或方法可以通过调用List对象的remove()方法移除。属性也可以用相似的方式移除。调用FieldInfo或MethodInfo的getAttributes()获取一个属性列表,然后从列表中移除。

    3. 遍历方法体

    为了检查方法体中所有的字节码指令,CodeIterator非常有效。为了获取这个对象,按下面的方式来做:

    ClassFile cf = ... ;
    MethodInfo minfo = cf.getMethod("move");    // we assume move is not overloaded.
    CodeAttribute ca = minfo.getCodeAttribute();
    CodeIterator i = ca.iterator();
    

    CodeIterator对象允许你从头到尾逐条访问字节码指令。下面的方法是CodeIterator声明的部分方法:

    • void begin()

      移动到第一条指令。

    • void move(int index)

      移动到指定索引的指令。

    • boolean hasNext()

      如果还有指令,返回true。

    • int next()

      返回下一条指令的索引。

      注意,它不返回下一条操作码的索引。

    • int byteAt(int index)

      返回指定索引的正8位值。

    • int u16bitAt(int index)

      返回指定索引的正16位值。

    • int write(byte[] code, int index)

      将byte数组写入到指定索引。

    • void insert(int index, byte[] code)

      插入byte数组到索引。分支偏移量等被自动调整。

    下面的代码片段展示了方法体中包含的所有指令:

    CodeIterator ci = ... ;
    while (ci.hasNext()) {
        int index = ci.next();
        int op = ci.byteAt(index);
        System.out.println(Mnemonic.OPCODE[op]);
    }
    

    4. 产生字节码序列

    Bytecode对象表示字节码指令的序列。它是字节码的可变数组。示例代码如下:

    ConstPool cp = ...;    // constant pool table
    Bytecode b = new Bytecode(cp, 1, 0);
    b.addIconst(3);
    b.addReturn(CtClass.intType);
    CodeAttribute ca = b.toCodeAttribute();
    

    这产生了如下序列:

    iconst_3
    ireturn
    

    你也可以通过调用Bytecode的get()方法获取包含这个序列的字节数组。获取的数组可以被插入到另一个代码属性中。

    Bytecode提供了大量方法来添加特殊的指令到序列中,它提供addOpcode()来添加8位操作码和addIndex()来添加一个索引。每一个操作码的8位值都在Opcode接口中定义。

    用来添加特殊指令的addOpcode()和其它方法是自动维护最大栈深的,除非控制流不包含分支。这个值可以通过调用Bytecode对象的getMaxStack()获取。它也反应在由ByteCode对象创建的CodeAttribute对象中。为了重新计算方法体的最大栈深,调用CodeAttribute的computeMaxStack()方法。

    5. 注解(元标记)

    注解存储在类文件中,作为运行时不可见(或可见)的注解属性。这些属性可以从ClassFile,MethodInfo,FieldInfo对象中获取。调用这些对象的getAttribute(AnnotationsAttribute.invisibleTag)。更详细的说明,可以查看javassist.bytecode.AnnotationsAttribute类和javassist.bytecode.annotation包的javadoc手册。

    Javassist也允许你通过高级的API访问注解。如果你想要通过CtClass访问注解,调用CtClass或者CtBehavior的getAnnotations()方法。

    相关文章

      网友评论

        本文标题:Javassist之字节码级的API

        本文链接:https://www.haomeiwen.com/subject/umnjuftx.html