java 字节码工程(修改类文件、在加载时修改字节码)两种方法分别代码例子说明,代码包含详细注释,最后用表格对比两种方式差异
java 字节码工程(修改类文件、在加载时修改字节码)两种方法分别代码例子说明,代码包含详细注释,最后用表格对比两种方式差异
·
以下是两种 Java 字节码修改方法的代码示例及对比,包含详细注释:
方法 1:直接修改类文件(编译后但加载前)
实现思路
- 直接操作
.class
文件,修改字节码后生成新的类文件。 - 适用于需要持久化修改(如生成新类文件供后续使用)。
代码示例
import org.objectweb.asm.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
public class BytecodeModifier {
public static void main(String[] args) throws IOException {
// 1. 读取原始类文件(HelloWorld.class)
ClassReader reader = new ClassReader("HelloWorld");
// 2. 创建ClassWriter,用于生成修改后的字节码
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
// 3. 创建ClassVisitor,处理类的结构
ClassVisitor visitor = new ClassVisitor(ASM9, writer) {
@Override
public MethodVisitor visitMethod(
int access,
String name,
String descriptor,
String signature,
String[] exceptions
) {
// 4. 仅修改名为 "sayHello" 的方法
if ("sayHello".equals(name)) {
// 5. 创建MethodVisitor,覆盖原有方法体
return new MethodVisitor(ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) {
@Override
public void visitCode() {
// 6. 在方法开头插入指令:调用 System.out.println("Modified!")
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/io/PrintStream", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Modified!");
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
// 继续执行原有方法体
super.visitCode();
}
};
}
// 其他方法不修改,直接返回
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
// 7. 读取并处理类
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
// 8. 生成新的字节码并写入文件
byte[] modifiedBytes = writer.toByteArray();
Files.write(Paths.get("HelloWorldModified.class"), modifiedBytes);
System.out.println("类文件修改完成!");
}
}
说明
- ASM版本:使用
ASM9
对应 Java 17+ 的字节码版本。 - 修改逻辑:在
sayHello
方法开头插入一条System.out.println("Modified!")
指令。 - 输出结果:生成
HelloWorldModified.class
,后续运行时需使用修改后的类文件。
方法 2:加载时修改(通过Instrumentation API)
实现思路
- 在类加载时动态修改字节码,无需预先生成新文件。
- 通过
java.lang.instrument
包实现,需以 Java Agent 形式运行。
代码示例
(1) 代理类(Agent)
import java.lang.instrument.Instrumentation;
public class BytecodeAgent {
public static void premain(String args, Instrumentation inst) {
System.out.println("Agent已加载,注册转换器...");
inst.addTransformer(new ClassFileTransformer());
}
}
(2) 类字节码转换器
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import org.objectweb.asm.*;
public class ClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer
) {
// 仅处理 "HelloWorld" 类
if ("HelloWorld".equals(className)) {
ClassReader reader = new ClassReader(classfileBuffer);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
ClassVisitor visitor = new ClassVisitor(ASM9, writer) {
@Override
public MethodVisitor visitMethod(
int access,
String name,
String descriptor,
String signature,
String[] exceptions
) {
if ("sayHello".equals(name)) {
return new MethodVisitor(ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) {
@Override
public void visitCode() {
// 插入 System.out.println("Modified!")
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/io/PrintStream", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Modified!");
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
super.visitCode();
}
};
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
return writer.toByteArray();
}
return null; // 不修改其他类
}
}
(3) 启动Agent的配置文件(MANIFEST.MF)
Manifest-Version: 1.0
Premain-Class: BytecodeAgent
Can-Run-In-Parallel: true
运行命令
# 启动时指定Agent(需打包为jar)
java -javaagent:bytecode-agent.jar -cp . HelloWorld
对比表格
维度 | 直接修改类文件 | 加载时修改(Instrumentation) |
---|---|---|
修改时机 | 编译后但加载前(静态修改) | 类加载时动态修改(运行时) |
是否持久化修改 | 是(生成新类文件) | 否(仅在内存中生效) |
对已加载类的影响 | 需重新启动才能生效 | 可修改已加载的类(需支持 retransform) |
适用场景 | 需要持久化修改(如框架预处理) | 动态监控、AOP、运行时热修复 |
性能开销 | 无运行时开销 | 运行时有轻微开销(需遍历所有类) |
实现复杂度 | 简单(直接操作文件) | 复杂(需处理类加载生命周期、Agent机制) |
是否需要Agent | 否 | 是(必须通过Java Agent加载) |
类加载器限制 | 无 | 受限于类加载器权限(如第三方库的类) |
总结
- 直接修改类文件:适合预处理阶段生成新类文件,简单且持久化。
- 加载时修改:适合动态场景(如AOP、监控),但需处理Agent机制和类加载器问题。
更多推荐
所有评论(0)