`
wmuu
  • 浏览: 9580 次
  • 性别: Icon_minigender_1
  • 来自: 自己输入城市...
文章分类
社区版块
存档分类
最新评论

学用BCEL设计字节码的记录

    博客分类:
  • java
阅读更多
最近看了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的实现,会降低些实现难度。

1
0
分享到:
评论

相关推荐

    java字节码例子 可以动态修改类 bcel

    另一篇资源"学用BCEL设计字节码的记录(java) - 庸人自扰 - C++博客.mht"则可能更侧重于实际操作和应用案例。博客作者分享了他们在使用BCEL过程中的经验,包括常见的问题和解决方法,这对于我们理解和掌握BCEL的使用...

    JAVA字节码操作库 BCEL.7z

    2. **字节码生成**: 使用BCEL,开发者可以创建新的类或方法,或者修改现有的字节码指令。这对于实现动态代理、代码注入、运行时代码生成等场景非常有用。 3. **字节码操作**: BCEL提供了丰富的API,允许开发者插入...

    修改字节码 jclasslib

    例如,可以先使用jclasslib来理解一个类的字节码,然后利用这些工具进行动态或静态的字节码注入,以实现如性能优化、日志记录、事务管理等功能。 四、字节码操作的应用场景 1. **性能优化**:通过修改字节码,可以...

    Java字节码实现Aop

    主要的字节码工具包括ASM、BCEL、CGLIB和Byte Buddy等。 ASM是一个轻量级的字节码库,可以直接生成和修改Java类的字节码。它提供了低级别的API,可以精细地控制字节码生成,适合于实现复杂的AOP逻辑。例如,通过ASM...

    bcel-6.0 bcel-6.0

    6. **示例**:可能提供一些示例代码或演示应用,展示如何使用BCML进行字节码操作。 7. **许可证文件**:包含Apache License 2.0或其他相关许可协议,规定了使用库的条件。 8. **README**和`CHANGES`文件:提供项目...

    commons-bcel,阿帕奇公地BCEL.zip

    - **字节码混淆**:在软件发布时,可以使用BCEL对字节码进行混淆,提高代码的安全性。 - **AOP(面向切面编程)**:在运行时插入或修改代码,实现跨切面的逻辑,如日志记录、事务管理等。 - **元编程**:通过BCEL...

    bcel-5.2

    4. **字节码插桩**: 在已存在的字节码中插入额外的代码,用于日志记录、性能监控或事务处理等。 5. **JVM语言实现**: BCEL为实现新的JVM目标语言(如Groovy、Scala等)提供了基础工具。 **四、BCEL与Java字节码** ...

    一种基于Java字节码的软件设计信息提取方法.zip

    1. **字节码解析**:使用字节码解析库,如ASM、BCEL、Javassist等,读取并解析.class文件。这些库提供了API,允许开发者访问和操作字节码。 2. **类结构分析**:通过解析字节码,可以识别出类的公共与私有属性、...

    “字节码插桩“ Demo.rar

    这通常使用ASM、BCEL、Javassist等字节码库完成,这些库提供了方便的方法解析和操作字节码。 3. **插入字节码**:在分析了字节码后,根据需求,我们可以选择在特定的位置插入新的字节码指令。例如,如果我们想要在...

    ant-apache-bcel-1.6.3.jar.zip

    总结起来,"ant-apache-bcel-1.6.3.jar.zip"这个压缩包是Java开发者的重要工具,它整合了Ant的构建能力与BCEL的字节码操作功能,提供了在Java项目中进行复杂构建和代码操作的能力。了解并掌握这两个工具的使用,将对...

    行业分类-设备装置-一种向目标进程内注入Java字节码的方法及装置.zip

    1. **字节码注入的原理**:字节码注入通常通过动态代理、ASM、BCEL、Javassist等字节码操作库来实现。这些库允许程序在运行时动态生成或修改字节码,然后通过ClassLoader加载到JVM中,使得修改后的字节码影响目标...

    JavaBytecodeAnalyzer:解析学习 Java 字节码

    - 开源库如ASM、BCEL、Javassist:提供了API,可以直接操作和生成字节码。 - IntelliJ IDEA和Eclipse等IDE也提供了查看字节码的功能。 掌握Java字节码分析能力对Java开发者来说是一项高级技能,它不仅可以提升你...

    asm-transformations e文

    例如,添加日志记录、安全验证等功能,这些功能可以通过特定的字节码转换模式来实现。 2. **复杂转换模式的组合**: 通过将简单的字节码转换模式组合起来,可以构建出更为复杂的转换逻辑,以满足不同场景下的需求。 3...

    JavaThings:共享与Java相关的东西-Java安全漫谈笔记相关内容

    Java安全漫谈目录 人口统计字节码:远程字节码加载: 系统默认的defineClass加载字节码演示: 使用TemplatesImpl加载字节码演示: 使用BCEL加载字节码:反序列化:最简单的Transformer演示: 我简化的 ,更方便大家...

    java实现根据方法查看方法参数名称

    要获取参数名称,我们需要借助于字节码解析库,例如ASM、BCEL或javassist。 1. ASM库:ASM是一个强大的Java字节码操控和分析框架。我们可以使用ASM的ClassReader类读取字节码,并通过MethodVisitor接口访问方法信息...

    03 - ASM和ClassFile - 简书1

    这个结构包括了一些关键字段,如魔数(magic)用于识别文件类型,版本号(minor_version和major_version)记录了字节码的版本,以及常量池(constant_pool)、访问标志(access_flags)、当前类索引(this_class)、...

    javassist-3.9.0.GA.jar.zip

    Javaassist是一个开源库,主要用在Java编程中,它提供了对字节码操作的强大功能,使得程序员可以在运行时动态修改类或创建新的类。这个"javassist-3.9.0.GA.jar.zip"文件包含了Javaassist库的版本3.9.0 GA,这是一个...

    Btrace 学习1

    BCEL (Byte Code Engineering Library) 是一个用于处理 Java 字节码的库,BTrace 在注入探针时可能使用了 BCEL 来生成和操作字节码。BCEL 提供了工具来解析、创建、修改和打印 Java 类文件。 7. **使用场景**: -...

    JAVAAssist jar包 ,最新发布,4月16号更新

    JavaAssist库通过提供一系列API,使得开发者无需深入理解复杂的ASM或BCEL等底层字节码操作框架,就能方便地实现对字节码的操作。这包括添加、删除或修改类的方法、字段和构造器,以及插入或替换代码块,甚至可以创建...

    java解析class文件

    在实际开发中,解析Class文件的工具和库有很多,如javap(Java字节码反汇编器)、ASM、BCEL、Javassist等。这些工具可以帮助开发者深入理解字节码,甚至自动生成或修改字节码,实现一些高级功能。 总而言之,Java...

Global site tag (gtag.js) - Google Analytics