项目中有个需求:在不修改源代码的情况下,替换某个类的引用为我们自己的实现。用一个类似的简单例子来说明:
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;
}
这是一个简单例子,从它上面可以看到“神奇”的表现,就像以前看很多框架的神奇之处一样,到头来都是背后做了很多不为人知的事情。同时为了这个例子,也学习到了很多以前很少关注的知识,这才是最大的收获。
分享到:
相关推荐
这篇博客文章《java字节码例子 可以动态修改类 bcel》(链接:https://andilyliao.iteye.com/blog/899925)可能详细介绍了如何使用BCEL进行字节码操作。虽然没有具体的描述内容,但可以推测文章会涵盖以下知识点: ...
BCML是一个Java字节码操纵和分析框架,它允许开发者对Java类文件进行操作和分析,包括创建、修改和动态生成。这个工具主要用于生成和操作Java虚拟机(JVM)字节码,常用于编译器、动态代理、代码分析和优化等场景。 ...
Java字节码操作库BCEL(Byte Code Engineering Library)是一个强大的工具,用于分析、修改和创建Java类文件。它是Apache软件基金会的Jakarta项目的一部分,为开发者提供了对字节码的底层控制,使得开发者能够在运行...
- **动态代理**: 在Java中,BCEL可以用来生成动态代理类,实现在运行时为接口提供实现,例如在Spring AOP中就有类似的应用。 - **代码插桩**: 通过在现有字节码中插入新的指令,可以实现代码监控、性能分析等功能。...
1. **字节码**:Java字节码是Java虚拟机(JVM)运行的二进制代码,它由javac编译器将源代码编译成。每个类都有一个`.class`文件,其中包含了该类的字节码。 2. **API组件**:BCEL包含一系列接口和类,如`ClassGen`,...
《Ant Apache BCEL 1.6.4:Java字节码操作库的深入解析》 在Java开发领域,Ant是一个广泛使用的自动化构建工具,而Apache BCEL(Byte Code Engineering Library)则是Ant的一个重要扩展,提供了对Java字节码的生成...
- **元编程**:通过BCEL在运行时创建和修改类,实现元编程能力。 3. **使用方法**: - **引入依赖**:在Maven项目中,可以通过在pom.xml文件中添加Apache Commons BCEL的依赖来引入库。 - **基本操作**:首先,...
JAVA源码JAVA字节码操作库BCEL
Java字节码操作库BCEL(Byte Code Engineering Library)是一个强大的工具,主要用于处理Java类文件。它提供了对字节码的全面访问和控制,允许开发者分析、修改和创建新的Java类。BCEL在许多领域都有应用,如动态...
BCEL代码解析工具 ,执行java -jar BCELCode.jar -d file.txt 即可解密。
Java字节码操作库BCEL(Byte Code Engineering Library)是一个强大的工具,主要用于处理Java字节码,它允许开发者分析、创建、修改以及动态生成类。BCEL是Apache软件基金会下的Jakarta项目的一部分,广泛应用于软件...
java资源JAVA字节码操作库 BCEL提取方式是百度网盘分享地址
2. **RandomAccessFile类**:Java提供了`java.io.RandomAccessFile`类来实现文件的随机读写。它可以定位到文件的任意位置进行读写,非常适合处理大文件或需要频繁跳转的场景。使用`RandomAccessFile`时,需要传入...
通过BCEL,开发者可以创建新的类、接口,或者在已有的类上添加、修改方法和字段,甚至可以实现复杂的代码转换和分析任务。 **二、BCEL核心组件** 1. **ClassGen**: 这个类提供了对Java类的全面访问,包括类名、...
它允许开发者分析、创建、修改以及适应Java类文件,从而深入理解并操纵Java虚拟机(JVM)的工作原理。这个库在很多场景下都非常有用,比如动态代理、代码分析、性能监控、代码混淆等。 1. **字节码操作**:BCEL提供...
BCEL提供了对Java类文件的完整生命周期支持,包括读取、修改和写入。 在我们的压缩包"ant-apache-bcel.jar.zip"中,有两个主要的文件:"ant-apache-bcel.jar"和"ant.license.txt"。前者是Ant与Apache BCEL整合的库...
3. **Instruction和ConstantPool**: BCEL将Java字节码指令表示为一个个Instruction对象,同时提供了ConstantPool类来管理类文件中的常量池。 三、BCEL的主要功能 1. **字节码生成**: BCEL可以生成符合Java字节码...