zoukankan      html  css  js  c++  java
  • JVM Class字节码之三-使用BCEL改变类属性

    使用BCEL动态改变Class内容

    之前对Class文件中的常量池,Method的字节码指令进行了说明。
    JVM Class详解之一
    JVM Class详解之二 Method字节码指令
    现在我们开始实际动手,使用BCEL改变字节码指令,对Class文件进行功能扩充。

    先介绍下BCEL全程Apache Byte Code Engineering Library,BCEL 每项内容操作在JVM汇编语言的级别

    HelloWorld搞起

    这个case我们需要给Programmer类做功能扩展,Programmer 职责进行了变化,除了要Coding以外,在每次Coding之前需要先做Plan,所以需要在do Coding信息输出之前输出 "doBcelPlan..." 信息。
    Demo

    public class Programmer implements Person {
    
        @Override
        public void doCoding() {
            System.out.println("do Coding...");
        }
    
    }
    

    期望效果

        @Override
        public void doCoding() {
             doPlan();
             System.out.println("do Coding...");
        }
    
        private void doPlan() {
             System.out.println("do Plan...");
        }
    

    需要做什么

    针对我们的期望结果我们需要做以下三点

    1. 增加一个doBcelPlan方法
    2. 在doCoding方法中调用doBcelPlan方法
    3. 在常量池中加入方法的声明,常量等其它使用到的变量和方法。

    工程先引入BCEL的依赖Pom中追加即可

            <dependency>
                <groupId>asm</groupId>
                <artifactId>asm</artifactId>
                <version>3.1</version>
            </dependency>
            <dependency>
                <groupId>asm</groupId>
                <artifactId>asm-tree</artifactId>
                <version>3.1</version>
            </dependency>
    

    1. 先使用BCEL 加载需要编辑的Class

            JavaClass clazz = Repository.lookupClass(Programmer.class);
            ClassGen classGen = new ClassGen(clazz);
            ConstantPoolGen cPoolGen = classGen.getConstantPool(); // 常量池信息
    

    2. 在常量池中增加一个MethodRef doBcelPlan

        int methodIndex = cPoolGen.addMethodref("byteCode.decorator.Programmer", "doBcelPlan", "()V");    // 在常量池中增加一个方法的声明返回methodIndex为声明在常量池中的位置索引
    

    第一个参数的去路径类名
    第二个参数是方法名称
    第三个方法返回类型 ()V 是void类型
    方法返回类型描述参考
    screenshot

    3. 在常量池中增加一个String类型的Filed

    因为有System.out.println("doBcelPlan")语句 
    doBcelPlan中的System.out 变量和println方法再doCoding中已经使用所有已经在常量池中了
    screenshot

        int stringIndex = cPoolGen.addString("doBcelPlan...");// 在常量池中增加一个Field的声明返回stringIndex为声明在常量池中的位置索引
    

    注意这里需要记录追加方法和Filed的index后面需要使用。

    4. 然后创建doBcelPlan方法的实体的字节码指令

    调用System.out变量和println方法 具体的字节码指令参数 上一节内容有说明 参考上一节文档 JVM Class详解之二 Method字节码指令

    InstructionList instructionDoPlan = new InstructionList();  // 字节码指令信息 
    instructionDoPlan.append(new GETSTATIC(17));  // 获取System.out常量
    instructionDoPlan.append(new LDC(stringIndex));  // 获取String Field信息
    instructionDoPlan.append(new INVOKEVIRTUAL(25)); // 调用Println方法
    instructionDoPlan.append(new RETURN());    // return 结果
    

    screenshot
    其中17,25都是常量池的引用参见下图,将原先的Programmer类编译后使用javap -versobse XXX.class 可以查看常量池信息。
    screenshot

    stringIndex 是引用第三步追加常量池String Field soBcelPlan

    5. 生成doBcelPlan方法

    MethodGen doPlanMethodGen = new MethodGen(1, Type.VOID, Type.NO_ARGS, null, "doBcelPlan",
    classGen.getClassName(), instructionDoPlan, cPoolGen);
    classGen.addMethod(doPlanMethodGen.getMethod());
    

    方法的声明并追加到classGen中。
    这样doBcelPlan方法就追加成功了。接下来我们需要找到doCoding方法,在方法中追加doBcelPlan的调用。

    6. 找到并修正doCoding方法

            Method[] methods = classGen.getMethods();
            for (Method method : methods) {
                String methodName = method.getName();
                if ("doCoding".equals(methodName)) {
                    MethodGen methodGen = new MethodGen(method, clazz.getClassName(), cPoolGen);
                    InstructionList instructionList = methodGen.getInstructionList();
                    InstructionHandle[] handles = instructionList.getInstructionHandles();
                    InstructionHandle from = handles[0];
                    InstructionHandle aload = instructionList.append(from, new ALOAD(0));
                    instructionList.append(aload, new INVOKESPECIAL(methodIndex));
                    classGen.replaceMethod(method, methodGen.getMethod());
                }
            }
    

    InstructionList 是当前方法中的字节码指令,我们append了两个指令ALOAD和INVOKESPECIAL。实现doBcelPlan的调用。

    7. 将编辑后的Class输出

            JavaClass target = classGen.getJavaClass();
            target.dump("D:\AliDrive\bytecode\bcel\Programmer.class");
    

    将修改后的字节码输出来看下,使用JD打开OK
    screenshot

    可以看到经过编辑后的Class文件输出结果同我们预期的是一样的
    Done!

    from: https://yq.aliyun.com/articles/7243?spm=5176.100239.blogcont7241.37.db8GKF

  • 相关阅读:
    【STL源码剖析读书笔记】【第6章】算法之inplace_merge算法
    Python学习 过程中零散知识点的总结
    Python 从零学起(纯基础) 笔记 (二)
    Python 从零学起(纯基础) 笔记(一)
    用select实现监控终端输入
    实现socket非阻塞设置
    exit(0)、exit(1)、exit(-1)的区别
    解决bind错误 bind: Address already in use
    UDP编程中client和server中使用recvfrom和sendto的区别
    linux安装zookeeper
  • 原文地址:https://www.cnblogs.com/KingIceMou/p/6967546.html
Copyright © 2011-2022 走看看