Javassist之字节码级的API

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

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

1. 获取ClassFile对象

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

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

1
2
3
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非常有效。为了获取这个对象,按下面的方式来做:

1
2
3
4
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数组到索引。分支偏移量等被自动调整。

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

1
2
3
4
5
6
CodeIterator ci = ... ;
while (ci.hasNext()) {
int index = ci.next();
int op = ci.byteAt(index);
System.out.println(Mnemonic.OPCODE[op]);
}

4. 产生字节码序列

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

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

这产生了如下序列:

1
2
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()方法。

坚持原创技术分享,您的支持将鼓励我继续创作!