最近看了Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码,网址是http://www-128.ibm.com/developerworks/cn/java/j-dyn0414/
其中的示范代码解释的不是很详细,这方面的中文资料又少,只好自己花时间看下去。bcel的类库却是不够友好,api文档也是走马观花的点一下,很多函数没有说明。理解基本靠猜,还好有个示范代码。下面对自己理解的东西做个记录,详细的代码可以到上面的链接下载。
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.Type;
public class BCELTiming {
/**
* Add timing wrapper to method of class. The method can accept any
* arguments and return any type (including void), but must be a normal
* (non-static, non-initializer) method to be used with this code as
* currently implemented. Handling the other types of methods would not
* involve any fundamental changes to the code.
*
* @param cgen
* generator for class being modified
* @param method
* current method to be enhanced with timing wrapper
*/
private static void addWrapper(ClassGen cgen, Method method) {
// set up the construction tools
InstructionFactory ifact = new InstructionFactory(cgen);
InstructionList ilist = new InstructionList();
ConstantPoolGen pgen = cgen.getConstantPool();
String cname = cgen.getClassName();
MethodGen wrapgen = new MethodGen(method, cname, pgen);
wrapgen.setInstructionList(ilist);
// rename a copy of the original method
MethodGen methgen = new MethodGen(method, cname, pgen);
cgen.removeMethod(method);
String iname = methgen.getName() + "$impl";
methgen.setName(iname);
cgen.addMethod(methgen.getMethod());
//以上是一下初始化的工作
// compute the size of the calling parameters
// operand stack操作数堆栈
Type[] types = methgen.getArgumentTypes(); // 取出参数类型数组
// solt代表本地变量的堆栈偏移量,里头储存了调用methen代表的函数的参数
int slot = methgen.isStatic() ? 0 : 1; // 这种方式与Java如何处理方法调用有关。对于非静态的方法,每次调用的第一个(隐藏的)参数是目标对象的this引用(就是位置0储存的内容)。
for (int i = 0; i < types.length; i++) {
slot += types[i].getSize();// 累计个个参数类型的长度,
}
// 现在solt指向最后一个参数的下一个位置
// save time prior to invocation
// 调用静态的long java.lang.System.currentTimeMillis()方法,调用结束后函数的返回的long类型的值会压入operand stack操作数堆栈
ilist.append(ifact.createInvoke("java.lang.System",
"currentTimeMillis", Type.LONG, Type.NO_ARGS,
Constants.INVOKESTATIC));
ilist.append(InstructionFactory.createStore(Type.LONG, slot));// 将operand stack的top保存到本地变量堆栈的slot位置,operand stack弹出long值
// call the wrapped method
int offset = 0; // 偏移量
short invoke = Constants.INVOKESTATIC; // 预先设置为调用静态函数
if (!methgen.isStatic()) { // 如果不是调用静态函数,将调用的第一个(隐藏的)参数(目标对象的this引用)压入operand stack
ilist.append(InstructionFactory.createLoad(Type.OBJECT, 0));
offset = 1;// 偏移量加1
invoke = Constants.INVOKEVIRTUAL;// 设置为调用非静态函数
}
for (int i = 0; i < types.length; i++) { // 遍历所有参数
Type type = types[i];
ilist.append(InstructionFactory.createLoad(type, offset)); // 按参数类型把参数一个个从本地变量堆栈取出,压入operand stack
offset += type.getSize();
}
Type result = methgen.getReturnType();// 取得要调用函数的返回值类型
ilist.append(ifact.createInvoke(cname, iname, result, types, invoke));// 调用方法名为iname的函数
// store result for return later
if (result != Type.VOID) {
ilist.append(InstructionFactory.createStore(result, slot + 2)); // 将名为iname的函数返回值复制到本地变量堆栈的slot+2的位置上
}
// print time required for method call
// 获取静态对象java.lang.System.out的引用,返回值压入operand stack
ilist.append(ifact.createFieldAccess("java.lang.System", "out",
new ObjectType("java.io.PrintStream"), Constants.GETSTATIC));
ilist.append(InstructionConstants.DUP);// 取operand stack的top,压入operand stack。完成后load_stack的头两个元素是静态对象java.lang.System.out的引用
ilist.append(InstructionConstants.DUP);// 取operand stack的top,压入operand stack。现在有3个java.lang.System.out的引用。供下面3次调用out.print()函数使用
String text = "Call to method " + methgen.getName() + " took ";
ilist.append(new PUSH(pgen, text));// 将text放入pgen(代表常量池),并把其在pgen的引用压入operand stack(供out.print(Sting)调用的参数)
ilist.append(ifact.createInvoke("java.io.PrintStream", "print",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));// 调用结束,operand stack弹出一个String的引用和一个out的引用(还剩2个out),函数没有返回值
ilist.append(ifact.createInvoke("java.lang.System",
"currentTimeMillis", Type.LONG, Type.NO_ARGS,
Constants.INVOKESTATIC));// 调用java.lang.System.currentTimeMillis()方法,调用结束后函数的返回的long类型的值会压入堆栈operand stack
ilist.append(InstructionFactory.createLoad(Type.LONG, slot));// 从本地变量堆栈的slot位置载入先前储存的long值,压入operand stack
ilist.append(InstructionConstants.LSUB);// 调用long的减法指令,弹出2个long值,并把结果压入operand stack,现在operand stack的top第一个是long,第二个是out的引用
ilist.append(ifact.createInvoke("java.io.PrintStream", "print",
Type.VOID, new Type[] { Type.LONG }, Constants.INVOKEVIRTUAL));// 调用out.print(long)方法
ilist.append(new PUSH(pgen, " ms."));// 将String对象" ms."放入pgen,并把其在pgen的引用压入operand stack(供out.print(Sting)调用的参数)
ilist
.append(ifact.createInvoke("java.io.PrintStream", "println",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
// return result from wrapped method call
if (result != Type.VOID) {
ilist.append(InstructionFactory.createLoad(result, slot + 2));// 处理返回值,如果不为空,从本地对象堆栈的slot+2位置读取指定类型的返回值压入operand stack
}
ilist.append(InstructionFactory.createReturn(result)); //调用处理返回值的指令,result为返回值的类型
//下面是一下扫尾工作
// finalize the constructed method
wrapgen.stripAttributes(true);
wrapgen.setMaxStack();
wrapgen.setMaxLocals();
cgen.addMethod(wrapgen.getMethod());
ilist.dispose();
}
public static void main(String[] argv) {
if (argv.length == 2 && argv[0].endsWith(".class")) {
try {
JavaClass jclas = new ClassParser(argv[0]).parse();
ClassGen cgen = new ClassGen(jclas);
Method[] methods = jclas.getMethods();
int index;
for (index = 0; index < methods.length; index++) {
if (methods[index].getName().equals(argv[1])) {
break;
}
}
if (index < methods.length) {
addWrapper(cgen, methods[index]);
FileOutputStream fos = new FileOutputStream(argv[0]);
cgen.getJavaClass().dump(fos);
fos.close();
} else {
System.err.println("Method " + argv[1] + " not found in "
+ argv[0]);
}
} catch (IOException ex) {
ex.printStackTrace(System.err);
}
} else {
System.out.println("Usage: BCELTiming class-file method-name");
}
}
}
相对javassist,bcel确实比较复杂。但是bcel给我的感觉比较自由,有种一切尽在掌握的感觉,这一点比较我喜欢。
虽然自由,但是用bcel写一个类的实现绝对不会是一件让人开心的事情,如果碰巧你的实现又稍微复杂了点(比如实现了xx接口,又增加了一些函数和成员变量)。而且你的实现未必会比javac编译出来的代码效率高(不考虑jvm的动态优化)。
不过,先把你要实现的类的代码写出来,按照代码来写bcel的实现,会降低些实现难度。
其中的示范代码解释的不是很详细,这方面的中文资料又少,只好自己花时间看下去。bcel的类库却是不够友好,api文档也是走马观花的点一下,很多函数没有说明。理解基本靠猜,还好有个示范代码。下面对自己理解的东西做个记录,详细的代码可以到上面的链接下载。
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.Type;
public class BCELTiming {
/**
* Add timing wrapper to method of class. The method can accept any
* arguments and return any type (including void), but must be a normal
* (non-static, non-initializer) method to be used with this code as
* currently implemented. Handling the other types of methods would not
* involve any fundamental changes to the code.
*
* @param cgen
* generator for class being modified
* @param method
* current method to be enhanced with timing wrapper
*/
private static void addWrapper(ClassGen cgen, Method method) {
// set up the construction tools
InstructionFactory ifact = new InstructionFactory(cgen);
InstructionList ilist = new InstructionList();
ConstantPoolGen pgen = cgen.getConstantPool();
String cname = cgen.getClassName();
MethodGen wrapgen = new MethodGen(method, cname, pgen);
wrapgen.setInstructionList(ilist);
// rename a copy of the original method
MethodGen methgen = new MethodGen(method, cname, pgen);
cgen.removeMethod(method);
String iname = methgen.getName() + "$impl";
methgen.setName(iname);
cgen.addMethod(methgen.getMethod());
//以上是一下初始化的工作
// compute the size of the calling parameters
// operand stack操作数堆栈
Type[] types = methgen.getArgumentTypes(); // 取出参数类型数组
// solt代表本地变量的堆栈偏移量,里头储存了调用methen代表的函数的参数
int slot = methgen.isStatic() ? 0 : 1; // 这种方式与Java如何处理方法调用有关。对于非静态的方法,每次调用的第一个(隐藏的)参数是目标对象的this引用(就是位置0储存的内容)。
for (int i = 0; i < types.length; i++) {
slot += types[i].getSize();// 累计个个参数类型的长度,
}
// 现在solt指向最后一个参数的下一个位置
// save time prior to invocation
// 调用静态的long java.lang.System.currentTimeMillis()方法,调用结束后函数的返回的long类型的值会压入operand stack操作数堆栈
ilist.append(ifact.createInvoke("java.lang.System",
"currentTimeMillis", Type.LONG, Type.NO_ARGS,
Constants.INVOKESTATIC));
ilist.append(InstructionFactory.createStore(Type.LONG, slot));// 将operand stack的top保存到本地变量堆栈的slot位置,operand stack弹出long值
// call the wrapped method
int offset = 0; // 偏移量
short invoke = Constants.INVOKESTATIC; // 预先设置为调用静态函数
if (!methgen.isStatic()) { // 如果不是调用静态函数,将调用的第一个(隐藏的)参数(目标对象的this引用)压入operand stack
ilist.append(InstructionFactory.createLoad(Type.OBJECT, 0));
offset = 1;// 偏移量加1
invoke = Constants.INVOKEVIRTUAL;// 设置为调用非静态函数
}
for (int i = 0; i < types.length; i++) { // 遍历所有参数
Type type = types[i];
ilist.append(InstructionFactory.createLoad(type, offset)); // 按参数类型把参数一个个从本地变量堆栈取出,压入operand stack
offset += type.getSize();
}
Type result = methgen.getReturnType();// 取得要调用函数的返回值类型
ilist.append(ifact.createInvoke(cname, iname, result, types, invoke));// 调用方法名为iname的函数
// store result for return later
if (result != Type.VOID) {
ilist.append(InstructionFactory.createStore(result, slot + 2)); // 将名为iname的函数返回值复制到本地变量堆栈的slot+2的位置上
}
// print time required for method call
// 获取静态对象java.lang.System.out的引用,返回值压入operand stack
ilist.append(ifact.createFieldAccess("java.lang.System", "out",
new ObjectType("java.io.PrintStream"), Constants.GETSTATIC));
ilist.append(InstructionConstants.DUP);// 取operand stack的top,压入operand stack。完成后load_stack的头两个元素是静态对象java.lang.System.out的引用
ilist.append(InstructionConstants.DUP);// 取operand stack的top,压入operand stack。现在有3个java.lang.System.out的引用。供下面3次调用out.print()函数使用
String text = "Call to method " + methgen.getName() + " took ";
ilist.append(new PUSH(pgen, text));// 将text放入pgen(代表常量池),并把其在pgen的引用压入operand stack(供out.print(Sting)调用的参数)
ilist.append(ifact.createInvoke("java.io.PrintStream", "print",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));// 调用结束,operand stack弹出一个String的引用和一个out的引用(还剩2个out),函数没有返回值
ilist.append(ifact.createInvoke("java.lang.System",
"currentTimeMillis", Type.LONG, Type.NO_ARGS,
Constants.INVOKESTATIC));// 调用java.lang.System.currentTimeMillis()方法,调用结束后函数的返回的long类型的值会压入堆栈operand stack
ilist.append(InstructionFactory.createLoad(Type.LONG, slot));// 从本地变量堆栈的slot位置载入先前储存的long值,压入operand stack
ilist.append(InstructionConstants.LSUB);// 调用long的减法指令,弹出2个long值,并把结果压入operand stack,现在operand stack的top第一个是long,第二个是out的引用
ilist.append(ifact.createInvoke("java.io.PrintStream", "print",
Type.VOID, new Type[] { Type.LONG }, Constants.INVOKEVIRTUAL));// 调用out.print(long)方法
ilist.append(new PUSH(pgen, " ms."));// 将String对象" ms."放入pgen,并把其在pgen的引用压入operand stack(供out.print(Sting)调用的参数)
ilist
.append(ifact.createInvoke("java.io.PrintStream", "println",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
// return result from wrapped method call
if (result != Type.VOID) {
ilist.append(InstructionFactory.createLoad(result, slot + 2));// 处理返回值,如果不为空,从本地对象堆栈的slot+2位置读取指定类型的返回值压入operand stack
}
ilist.append(InstructionFactory.createReturn(result)); //调用处理返回值的指令,result为返回值的类型
//下面是一下扫尾工作
// finalize the constructed method
wrapgen.stripAttributes(true);
wrapgen.setMaxStack();
wrapgen.setMaxLocals();
cgen.addMethod(wrapgen.getMethod());
ilist.dispose();
}
public static void main(String[] argv) {
if (argv.length == 2 && argv[0].endsWith(".class")) {
try {
JavaClass jclas = new ClassParser(argv[0]).parse();
ClassGen cgen = new ClassGen(jclas);
Method[] methods = jclas.getMethods();
int index;
for (index = 0; index < methods.length; index++) {
if (methods[index].getName().equals(argv[1])) {
break;
}
}
if (index < methods.length) {
addWrapper(cgen, methods[index]);
FileOutputStream fos = new FileOutputStream(argv[0]);
cgen.getJavaClass().dump(fos);
fos.close();
} else {
System.err.println("Method " + argv[1] + " not found in "
+ argv[0]);
}
} catch (IOException ex) {
ex.printStackTrace(System.err);
}
} else {
System.out.println("Usage: BCELTiming class-file method-name");
}
}
}
相对javassist,bcel确实比较复杂。但是bcel给我的感觉比较自由,有种一切尽在掌握的感觉,这一点比较我喜欢。
虽然自由,但是用bcel写一个类的实现绝对不会是一件让人开心的事情,如果碰巧你的实现又稍微复杂了点(比如实现了xx接口,又增加了一些函数和成员变量)。而且你的实现未必会比javac编译出来的代码效率高(不考虑jvm的动态优化)。
不过,先把你要实现的类的代码写出来,按照代码来写bcel的实现,会降低些实现难度。