`
langyu
  • 浏览: 888849 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

[Java拾遗]初次尝试BCEL:修改类实现的例子

    博客分类:
  • java
jvm 
阅读更多

    项目中有个需求:在不修改源代码的情况下,替换某个类的引用为我们自己的实现。用一个类似的简单例子来说明:


public class CarHolder {
    private Car car;
    public CarHolder() {
        init();
    }

    private void init() {
        car = new Benz();
    }

    public void displayCarName() {
        System.out.println(car.getCarName());
    }
}


    正常情况下执行这个类,当调用displayCarName这个方法时会得到"Hi, my name is Benz",但需求是当调用displayCarName时需要输出"Yeah, I am BMW",其实是用BMW这个Car的实现类替换已经预先定义的Benz实现。

    需求的本质是修改CarHolder的字节码,让其在运行期的行为与源代码上看起来不一样。修改字节码有两个时机:1. 静态修改,把java文件编译后的class文件替换成我们修改后的class文件,classLoader会加载我们的实现;2. 动态修改,通过特殊的classLoader加载源class文件并修改成我们想要的实现,或是在classLoader加载class文件时,通过JDK Instrumentation组件所提供的ClassFileTransformer机制修改字节码(java.lang.instrument.ClassFileTransformer)。两种策略的结果是一致的,JVM都能执行修改后的字节码。

    当前可以修改字节码的组件有十几种,有些体现在源代码级别,有些体现在JVM执行指令级别。最终我选择尝试下BCEL,就是因为通过它可以熟悉class文件的组织结构和JVM指令集的细节,同时它还被加入到sun的内部JDK中,多个框架都在用它,也是学习的一种契机。

    在使用BCEL之前,我翻了JVM规范里相关的内容,大致理解了常量池的使用及JVM的常用指令。最终使用的代码像这样


public class StaticChangedCode {

	public static void main(String[] args) {
		try {
			//以对象的方式操纵class文件
			JavaClass clazz = Repository.lookupClass(CarHolder.class);
			ClassGen classGen = new ClassGen(clazz);

			//由于是替换旧类型,所以对于当前常量池中没有的类型/方法/属性等都得一一加入
			//常量池项的引用索引,在方法指令中需要调用
			ConstantPoolGen cPoolGen = classGen.getConstantPool();
			int value = cPoolGen.addClass("bcel.changeimpl.BMW");
			int methodIndex = cPoolGen
                         .addMethodref("bcel.changeimpl.BMW", "<init>", "()V");
			int fieldIndex = cPoolGen
                                 .addFieldref("bcel.changeimpl.CarHolder", 
                                        "car", "Lbcel/changeimpl/Car;");

			//获取想要操纵的方法,因为我知道init方法排行第二,所以这里就写死了
			Method sourceMethod = classGen.getMethods()[1];
			MethodGen methodGen = new MethodGen(sourceMethod, clazz.getClassName(), cPoolGen);
			InstructionList instructionList = methodGen.getInstructionList();

			//从原有的指令列表中删去初始化Benz的那部分指令
			InstructionHandle[] handles = instructionList.getInstructionHandles();
			InstructionHandle from = handles[1];
			InstructionHandle to = handles[4];
			instructionList.delete(from, to);

			//这里开始添加初始化BMW的对象
			//对象的创建指令有三步:1. new, 在heap上创建对象结构,分配内存; 
            //2. dup, 在操作数栈上保留对刚创建对象的引用,然后复制此引用;
			//3. invokespecial,利用刚复制出的对象引用标识出对象,然后调用它的<init>方法
			//经过上面三步后,对象就可以被使用了,此时做赋值动作,将当前对象赋给car这个变量
			InstructionHandle newHandle = instructionList
                                   .append(handles[0], new NEW(value));
			InstructionHandle dumpHandle = instructionList
                                    .append(newHandle, new DUP());
			InstructionHandle initHandle = instructionList
                            .append(dumpHandle, new INVOKESPECIAL(methodIndex));
			instructionList.append(initHandle, new PUTFIELD(fieldIndex));

			//因为上面经历过指令修改的过程,所以这里用新指令的方法去替代旧指令的方法
			classGen.replaceMethod(sourceMethod, methodGen.getMethod());

			//工作完成,再次生成新的class文件
			JavaClass target = classGen.getJavaClass();
			target.dump("bin/bcel/changeimpl/CarHolder.class");

		} catch (Exception e) {
			// TODO: handle exception
		}

	}

}


    之后当你再次运行CarHolder时,它的结果就改变了。上面是静态修改的例子,如果是想要动态修改,那么就利用自己的ClassFileTransformer,把byte数组转化成可操纵的对象,然后与上面的流程一样,最后再返回byte数组给classloader


public byte[] transform(ClassLoader loader, String className,
			Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
			byte[] classfileBuffer) throws IllegalClassFormatException {
		try {
			//由byte数组生成JavaClass对象
			InputStream inStream = new ByteArrayInputStream(classfileBuffer);
			JavaClass jc = new ClassParser(inStream, className).parse();
			
			//这里与上面流程是一样
			
			//再次转化成byte数组,然后给classloader
			JavaClass final = ***;
			return final.getBytes();
		} catch (Exception e) {
			// TODO: handle exception
		}
		return classfileBuffer;
}


    这是一个简单例子,从它上面可以看到“神奇”的表现,就像以前看很多框架的神奇之处一样,到头来都是背后做了很多不为人知的事情。同时为了这个例子,也学习到了很多以前很少关注的知识,这才是最大的收获。
1
1
分享到:
评论
3 楼 s929498110 2012-04-07  
博主你好, 我有一个类似你这个的需求。

具体就是:

class A extends B
new A() 时候肯定首先是调用B的构造方法,我想在B的构造方法中将A中的某个方法修改了,这样可以吗? 静态方法呢?

   希望博主不吝赐教
2 楼 budairenqin 2012-02-01  
你好,例子貌似有点问题吧?
int fieldIndex = cPoolGen  
16.                                 .addFieldref("bcel.changeimpl.CarHolder",   
17.                                        "car", "Lbcel/changeimpl/Car;");

上面这句加上之后常量池里面不是两个Lbcel/changeimpl/Car了?ClassLoader装载的时候通不过验证吧?
1 楼 jiafu1115 2011-09-16  
你这个需求很有意思,哈哈

相关推荐

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

    这篇博客文章《java字节码例子 可以动态修改类 bcel》(链接:https://andilyliao.iteye.com/blog/899925)可能详细介绍了如何使用BCEL进行字节码操作。虽然没有具体的描述内容,但可以推测文章会涵盖以下知识点: ...

    bcel-6.0 bcel-6.0

    BCML是一个Java字节码操纵和分析框架,它允许开发者对Java类文件进行操作和分析,包括创建、修改和动态生成。这个工具主要用于生成和操作Java虚拟机(JVM)字节码,常用于编译器、动态代理、代码分析和优化等场景。 ...

    java源码:JAVA字节码操作库 BCEL.zip

    Java字节码操作库BCEL(Byte Code Engineering Library)是一个强大的工具,用于分析、修改和创建Java类文件。它是Apache软件基金会的Jakarta项目的一部分,为开发者提供了对字节码的底层控制,使得开发者能够在运行...

    JAVA字节码操作库 BCEL.7z

    - **动态代理**: 在Java中,BCEL可以用来生成动态代理类,实现在运行时为接口提供实现,例如在Spring AOP中就有类似的应用。 - **代码插桩**: 通过在现有字节码中插入新的指令,可以实现代码监控、性能分析等功能。...

    JAVA字节码操作库 BCEL

    1. **字节码**:Java字节码是Java虚拟机(JVM)运行的二进制代码,它由javac编译器将源代码编译成。每个类都有一个`.class`文件,其中包含了该类的字节码。 2. **API组件**:BCEL包含一系列接口和类,如`ClassGen`,...

    ant-apache-bcel-1.6.4.jar.zip

    《Ant Apache BCEL 1.6.4:Java字节码操作库的深入解析》 在Java开发领域,Ant是一个广泛使用的自动化构建工具,而Apache BCEL(Byte Code Engineering Library)则是Ant的一个重要扩展,提供了对Java字节码的生成...

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

    - **元编程**:通过BCEL在运行时创建和修改类,实现元编程能力。 3. **使用方法**: - **引入依赖**:在Maven项目中,可以通过在pom.xml文件中添加Apache Commons BCEL的依赖来引入库。 - **基本操作**:首先,...

    JAVA源码JAVA字节码操作库BCEL

    JAVA源码JAVA字节码操作库BCEL

    基于Java的字节码操作库 BCEL.zip

    Java字节码操作库BCEL(Byte Code Engineering Library)是一个强大的工具,主要用于处理Java类文件。它提供了对字节码的全面访问和控制,允许开发者分析、修改和创建新的Java类。BCEL在许多领域都有应用,如动态...

    BCEL类代码解析工具

    BCEL代码解析工具 ,执行java -jar BCELCode.jar -d file.txt 即可解密。

    基于java的字节码操作库 BCEL.zip

    Java字节码操作库BCEL(Byte Code Engineering Library)是一个强大的工具,主要用于处理Java字节码,它允许开发者分析、创建、修改以及动态生成类。BCEL是Apache软件基金会下的Jakarta项目的一部分,广泛应用于软件...

    java资源JAVA字节码操作库BCEL

    java资源JAVA字节码操作库 BCEL提取方式是百度网盘分享地址

    java 随机读写Java类文件

    2. **RandomAccessFile类**:Java提供了`java.io.RandomAccessFile`类来实现文件的随机读写。它可以定位到文件的任意位置进行读写,非常适合处理大文件或需要频繁跳转的场景。使用`RandomAccessFile`时,需要传入...

    bcel-5.2

    通过BCEL,开发者可以创建新的类、接口,或者在已有的类上添加、修改方法和字段,甚至可以实现复杂的代码转换和分析任务。 **二、BCEL核心组件** 1. **ClassGen**: 这个类提供了对Java类的全面访问,包括类名、...

    基于Java的实例源码-字节码操作库 BCEL.zip

    它允许开发者分析、创建、修改以及适应Java类文件,从而深入理解并操纵Java虚拟机(JVM)的工作原理。这个库在很多场景下都非常有用,比如动态代理、代码分析、性能监控、代码混淆等。 1. **字节码操作**:BCEL提供...

    ant-apache-bcel.jar.zip

    BCEL提供了对Java类文件的完整生命周期支持,包括读取、修改和写入。 在我们的压缩包"ant-apache-bcel.jar.zip"中,有两个主要的文件:"ant-apache-bcel.jar"和"ant.license.txt"。前者是Ant与Apache BCEL整合的库...

    bcel-6.5.0-src.zip

    3. **Instruction和ConstantPool**: BCEL将Java字节码指令表示为一个个Instruction对象,同时提供了ConstantPool类来管理类文件中的常量池。 三、BCEL的主要功能 1. **字节码生成**: BCEL可以生成符合Java字节码...

Global site tag (gtag.js) - Google Analytics