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