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类型
方法返回类型描述参考

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

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

    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 结果


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

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

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

时间: 2024-11-05 16:35:07

JVM Class字节码之三-使用BCEL改变类属性的相关文章

JVM的字节码iconst_&amp;amp;lt;n&amp;amp;gt;疑问

问题描述 一个运行测试的源程序: public class TestC1 {public void func() {int a = 6; int b = 5; int c = 1;}} 我们用命令javap -c TestC1,查看其字节码,如下: public void func(); Code: 0: bipush 6 2: istore_1 3: iconst_5 4: istore_2 5: iconst_1 6: istore_3 7: return} 为什么int值大于5的表示为bi

怎样动态改变类属性的作用域?

问题描述 比如说传进来的参数是一个类,然后这个类的属性作用域都是public,现在想把它的属性作用域全部改成private.请问各位朋友要怎样实现?

字节码及ASM使用

字节码及ASM使用 什么是字节码? 机器码 机器码(machine code)是CPU可直接解读的指令.机器码与硬件等有关,不同的CPU架构支持的硬件码也不相同. 字节码 字节码(bytecode)是一种包含执行程序.由一序列 op 代码/数据对 组成的二进制文件.字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码.通常情况下它是已经经过编译,但与特定机器码无关.字节码主要为了实现特定软件运行和软件环境.与硬件环境无关. 字节码的实现方式是通过编译器和虚拟机器.编译器

深入字节码 -- 计算方法执行时间

什么是字节码? java程序通过javac编译之后生成文件.class就是字节码集合,正是有这样一种中间码(字节码),使得scala/groovy/clojure等函数语言只用实现一个编译器即可运行在JVM上. 看看一段简单代码. public long getExclusiveTime() { long startTime = System.currentTimeMillis(); System.out.printf("exclusive code"); long endTime =

Java Class字节码知识点回顾

把之前的笔记重新整理了一下,发上来供对java Class文件结构的有兴趣的同学参考一下,也算对以前知识的回顾. Java Class文件打破了C或者C++等语言所遵循的传统,用这些传统语言写的程序通常首先被编译,然后被连接成单独的.专门支持特定硬件平台和操作系统的二进制文件.通常情况下,一个平台上的二进制可执行文件不能在其他平台上工作. Java Class文件是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的二进制文件,Class文件中包含了java虚拟机指令集和符号表以及若干其他辅

从字节码层面看“HelloWorld” (转)

一.HelloWorld 字节码生成 众所周知,Java 程序是在 JVM 上运行的,不过 JVM 运行的其实不是 Java 语言本身,而是 Java 程序编译成的字节码文件.可能一开始 JVM 是为 Java 语言服务的,不过随着编译技术和 JVM 自身的不断发展和成熟,JVM 已经不仅仅只运行 Java 程序.任何能编译成为符合 JVM 字节码规范的语言都可以在 JVM 上运行,比较常见的 Scala.Groove.JRuby等.今天,我就从大家最熟悉的程序"HelloWorld"

Java字节码

原文出处:https://www.ibm.com/developerworks/library/it-haggar_bytecode/index.html#opcode 作者:Peter Haggar 发表时间:2001 / 07 / 01 理解字节码可以使你变成一个更好的程序员 关于字节码的信息,以及这里提供的字节码,都是基于Java 2 SDK标准版v1.2.1 javac编译器的.其他编译器生成的字节码可能略有不同. 为什么要理解字节码? 字节码就是 Java 代码的中间表示,就像是汇编代

spring ,hibernate 都是用到了asm字节码技术,请问它们具体都拿ASM来实现了什么功能?或者说在哪个功能上用到的

问题描述 spring ,hibernate 都是用到了asm字节码技术,请问它们具体都拿ASM来实现了什么功能?或者说在哪个功能上用到的 我们查看Spring,主要使用了cglib动态代理来实现一些IOC或者AOP功能,但是这个和ASM好像是没关系?还有hibernate,在做数据持久化的时候主要用的是反射,那它有拿ASM干了什么呢? 解决方案 动态代理,hibernate的懒加载使用到了asm,spring的AOP也使用到了.你建立一个hibernate映射对象并使用懒加载配置的时候,在内存

Java编程的动态性,第7部分: 用BCEL设计字节码

Apache Byte Code Engineering Library (BCEL)可以深入 Java 类的字节码.可以用它 转换现有的类表示或者构建新的类,因为 BCEL 在单独的 JVM 指令级别上进行操作,所以可 以让您对代码有最强大的控制.不过,这种能力的代价是复杂性.在本文中,Java 顾问 Dennis Sosnoski 介绍了 BCEL 的基本内容,并引导读者完成一个示例 BCEL 应用程序,这 样您就可以自己决定是否值得以这种复杂性来换取这种能力. 在本系列的最后三篇文章中,我