随笔-30  评论-67  文章-0  trackbacks-0

最近看了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的实现,会降低些实现难度。

posted on 2006-05-31 16:44 含笑半步癫 阅读(2422) 评论(4)  编辑 收藏 引用 所属分类: java

评论:
# re: 学用BCEL设计字节码的记录(java) 2006-08-31 09:41 | lai
我尝试了编译这个程序,可是没有输出啊!估计是因为在StringBuilder中没有传进参数的原因,请问在eclipse中应该怎么做呢
  回复  更多评论
  
# re: 学用BCEL设计字节码的记录(java) 2006-09-07 22:47 | 含笑半步癫
main的函数是需要参数的。具体如何运行请参考上面给出的ibm的文章  回复  更多评论
  
# re: 学用BCEL设计字节码的记录(java) 2008-01-04 17:52 | allen
这个代码好像是丹尼斯写的 IBM网站Java动态编程系列文章一共8篇  回复  更多评论
  
# re: 学用BCEL设计字节码的记录(java) 2010-05-20 15:16 | ewwerpm
包org.apache.bcel.util.BCELifier和com.sun.org.apache.bcel.internal.util.BCELifier有什么区别?com.sun.org.apache.bcel.internal.util.BCELifier是装 JDK1.6的时候JRE里面自带的,org.apache.bcel.util.BCELifier需要从网上下载,类名字是一样的,不知道他们在使用的时候有什么区别?我只能用org.apache.bcel.util.BCELifier包将HelloWorld.class文件反编译成HelloWorldCreator.java文件,但是在执行 java HelloWorldCrator 的时候没用设想中的输出啊。
public class HelloWorld{
public static void main(String args[]){
System.err.println("Hello World through BCEL!");
}
}  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理