Java 字节码增强(ASM / ByteBuddy)
JVM 字节码基础
Class 文件结构
Java 源代码经过 javac 编译后生成 .class 字节码文件,其结构由 Java 虚拟机规范严格定义。一个 Class 文件以 8 字节的魔数和版本号开头,随后按顺序存储常量池、访问标志、类索引、字段表、方法表和属性表。
| 区域 | 类型 | 说明 |
|---|---|---|
| 魔数 | u4 | 固定值 0xCAFEBABE,标识文件类型 |
| 版本号 | u2 (minor) + u2 (major) | 如 major 61 对应 Java 17,major 65 对应 Java 21 |
| 常量池 | cp_info[] | 存放字面量和符号引用(类名/方法名/字段描述符等) |
| 访问标志 | u2 | 如 ACC_PUBLIC(0x0001) / ACC_ABSTRACT(0x0400) |
| 类索引 | u2 | 指向常量池中 CONSTANT_Class_info |
| 父类索引 | u2 | 指向父类的常量池索引 |
| 接口索引集合 | u2[] | 实现的接口列表 |
| 字段表 | field_info[] | 类字段/实例字段的定义 |
| 方法表 | method_info[] | 方法定义,包含字节码指令 |
| 属性表 | attribute_info[] | 附加信息(Code 属性/LineNumberTable/StackMapTable 等) |
// 通过 javap 查看 Class 文件结构
// 编译: javac Hello.java
// 反汇编: javap -v Hello.class
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Bytecode!");
}
}// javap -v Hello.class 输出片段
Classfile /path/to/Hello.class
Magic number: 0xCAFEBABE
Minor version: 0
Major version: 61 // Java 17
Constant pool:
#1 = Methodref #7.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #24 // "Hello, Bytecode!"
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
...
access flags: 0x0021 (ACC_PUBLIC, ACC_SUPER)
this class: #6 // Hello
super class: #7 // java/lang/Object字节码指令集
JVM 指令是一个字节长度的操作码(opcode),后跟零个或多个操作数。指令按功能分类如下。
加载与存储指令
| 指令 | 说明 |
|---|---|
aload_<n> | 将局部变量表中第 n 个引用型变量推送到操作数栈 |
iload_<n> | 将局部变量表中第 n 个 int 型变量推送到操作数栈 |
istore_<n> | 将操作数栈顶 int 值存入局部变量表第 n 个位置 |
astore_<n> | 将操作数栈顶引用值存入局部变量表第 n 个位置 |
ldc | 将常量池中的常量推送到操作数栈 |
bipush | 将一个 byte 类型常量推送到操作数栈 |
算术运算指令
| 指令 | 说明 |
|---|---|
iadd | 栈顶两个 int 值相加,结果入栈 |
isub / imul / idiv | 减法 / 乘法 / 除法 |
ineg | 栈顶 int 值取负 |
类型转换指令
| 指令 | 说明 |
|---|---|
i2l | int 转 long |
i2d | int 转 double |
d2i | double 转 int |
checkcast | 类型强转检查 |
对象创建与操作指令
| 指令 | 说明 |
|---|---|
new | 创建对象实例,分配堆内存 |
dup | 复制栈顶值(构造方法调用前需保留引用) |
getfield / putfield | 读取 / 写入实例字段 |
getstatic / putstatic | 读取 / 写入静态字段 |
arraylength | 获取数组长度 |
方法调用指令
| 指令 | 说明 |
|---|---|
invokevirtual | 调用实例方法(虚方法分派) |
invokespecial | 调用构造方法、私有方法、父类方法 |
invokestatic | 调用静态方法 |
invokeinterface | 调用接口方法 |
invokedynamic | 动态方法调用(Java 8 Lambda / 动态语言支持) |
返回与控制转移指令
| 指令 | 说明 |
|---|---|
return | void 方法返回 |
ireturn / areturn | 返回 int / 引用类型 |
ifeq | 栈顶为 0 则跳转 |
ifne / iflt / ifgt | 不等于 0 / 小于 0 / 大于 0 则跳转 |
goto | 无条件跳转 |
tableswitch / lookupswitch | switch-case 跳转 |
// 以下方法的字节码示例
public int add(int a, int b) {
return a + b;
}// 对应字节码:
iload_1 // 将局部变量表 index=1 (参数 a) 入栈
iload_2 // 将局部变量表 index=2 (参数 b) 入栈
iadd // 栈顶两 int 相加
ireturn // 返回栈顶 int操作数栈与局部变量表
每个方法被调用时,JVM 会在栈帧中创建局部变量表和操作数栈。
栈帧结构
| 组件 | 说明 |
|---|---|
| 局部变量表 | 存储方法参数和方法内部定义的局部变量,以 slot 为单位(long/double 占 2 slot,其余占 1 slot) |
| 操作数栈 | 方法执行过程中用于计算的临时栈空间 |
| 动态链接 | 指向运行时常量池的方法引用 |
| 返回地址 | 方法正常或异常返回时的恢复信息 |
栈帧布局示意:
+----------------------------+
| 返回值 (返回地址) |
+----------------------------+
| 动态链接 (常量池引用) |
+----------------------------+
| 操作数栈 |
| [0] [1] [2] ... [n] |
+----------------------------+
| 局部变量表 |
| slot[0] = this |
| slot[1] = arg0 |
| slot[2] = arg1 |
| slot[3] = localVar |
+----------------------------+局部变量表 slot 复用
为了节省栈帧空间,局部变量表中不重叠的变量可以复用同一个 slot。例如:
public void example() {
int a = 1; // a 占用 slot[1]
// ... 后续不再使用 a
int b = 2; // b 可以复用 slot[1](a 已无用)
}操作数栈深度
操作数栈的最大深度在编译期确定,存储在 Code 属性的 max_stack 字段中。深度不足会导致 VerifyError。
示例: a = b + c * 2;
指令序列:
iload_<b> // 栈深度 1
iload_<c> // 栈深度 2
iconst_2 // 栈深度 3
imul // 栈深度 2 (c*2 结果入栈)
iadd // 栈深度 1 (b+c*2 结果入栈)
istore_<a> // 栈深度 0 (结果存入局部变量表)
最大栈深度为 3ASM 字节码框架
ASM 核心 API
ASM 是一个轻量级、高性能的 Java 字节码操作框架,采用访问者设计模式。核心类包括:
| 类 | 说明 |
|---|---|
ClassReader | 读取并解析已有的 .class 文件,逐个访问类结构元素 |
ClassVisitor | 抽象访问者,访问到相应结构时回调对应方法 |
ClassWriter | ClassVisitor 的子类,将访问结果重新生成字节码 |
Opcodes | 常量定义接口,包含所有指令码和访问标志 |
MethodVisitor | 方法级别的访问者,用于读取或写入方法体字节码 |
<!-- Maven 坐标 -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7</version>
</dependency>ASM 访问者模式解析流程
流程示意:
+-----------+
| .class 文件 |
+-----------+
|
v
+-------------+
| ClassReader | ----> 读取字节码,解析各个部分
+-------------+
|
v
+---------------+
| ClassVisitor | ----> 访问者模式回调
| + visit() |
| + visitField()|
| + visitMethod()|
+---------------+
|
v
+-------------+
| ClassWriter | ----> 生成新的字节码
+-------------+
|
v
+-----------+
| byte[] |
+-----------+// ASM 核心 API 使用示例:读取并打印类信息
import org.objectweb.asm.*;
public class ClassInfoPrinter {
public static void print(String className) throws Exception {
ClassReader cr = new ClassReader(className);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cr.accept(new ClassVisitor(Opcodes.ASM9, cw) {
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
System.out.println("类名: " + name.replace('/', '.'));
System.out.println("版本: " + version);
System.out.println("父类: " + superName.replace('/', '.'));
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
System.out.println(" 方法: " + name + desc);
return super.visitMethod(access, name, desc, signature, exceptions);
}
}, 0);
}
}方法增强
通过 MethodVisitor 可以在方法的字节码前后插入自定义逻辑。
// 方法计时增强示例:为方法添加耗时统计
import org.objectweb.asm.*;
public class TimingClassAdapter extends ClassVisitor {
private String className;
public TimingClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
this.className = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
// 跳过构造方法和静态初始化块
if (!name.equals("<init>") && !name.equals("<clinit>") && mv != null) {
mv = new TimingMethodVisitor(mv, className, name);
}
return mv;
}
// 内部 MethodVisitor
private static class TimingMethodVisitor extends MethodVisitor {
private final String className;
private final String methodName;
public TimingMethodVisitor(MethodVisitor mv, String className, String methodName) {
super(Opcodes.ASM9, mv);
this.className = className;
this.methodName = methodName;
}
@Override
public void visitCode() {
// 方法进入时:long start = System.nanoTime();
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"java/lang/System", "nanoTime", "()J", false);
mv.visitVarInsn(Opcodes.LSTORE, 100); // 暂存到局部变量表 slot 100
super.visitCode();
}
@Override
public void visitInsn(int opcode) {
// 在 RETURN / ARETURN / IRETURN 等返回指令前插入计时输出
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
|| opcode == Opcodes.ATHROW) {
// long duration = System.nanoTime() - start;
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"java/lang/System", "nanoTime", "()J", false);
mv.visitVarInsn(Opcodes.LLOAD, 100);
mv.visitInsn(Opcodes.LSUB);
mv.visitVarInsn(Opcodes.LSTORE, 102); // 存 duration
// System.out.println(className + "#" + methodName + " took " + duration + "ns");
mv.visitFieldInsn(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
"java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn(className.replace('/', '.') + "#" + methodName + " took ");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/StringBuilder", "append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(Opcodes.LLOAD, 102);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/StringBuilder", "append",
"(J)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn("ns");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/StringBuilder", "append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/StringBuilder", "toString",
"()Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println",
"(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
@Override
public void visitEnd() {
super.visitEnd();
}
}
}// 使用上面的 Adapter 进行类转换
import org.objectweb.asm.*;
public class TimingAgent {
public static byte[] transform(byte[] classBytes) {
ClassReader cr = new ClassReader(classBytes);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
TimingClassAdapter adapter = new TimingClassAdapter(cw);
cr.accept(adapter, 0);
return cw.toByteArray();
}
}方法前插入与方法后插入的区别
| 插入时机 | 实现方式 | 说明 |
|---|---|---|
| 方法前插入 | 在 visitCode() 中追加指令 | 在方法原有逻辑之前执行 |
| 方法后插入 | 在 visitInsn() 中拦截 RETURN 等指令 | 在方法返回之前执行,不能改变原返回值 |
ASM 工具类
Type 与描述符
Type 类提供了 Java 类型与 JVM 内部描述符之间的转换:
import org.objectweb.asm.Type;
// 获取类型描述符
String desc = Type.getDescriptor(String.class); // "Ljava/lang/String;"
String desc = Type.getDescriptor(int.class); // "I"
String desc = Type.getDescriptor(void.class); // "V"
// 获取方法描述符
String methodDesc = Type.getMethodDescriptor(
Type.VOID_TYPE, // 返回值类型
Type.getType(String.class) // 参数类型
); // "(Ljava/lang/String;)V"
// 从内部名获取 Type
Type t = Type.getObjectType("java/lang/String");Label
Label 用于标记字节码中的跳转目标位置:
MethodVisitor mv = ...;
Label elseLabel = new Label();
Label endLabel = new Label();
// if (condition == 0) goto else
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitJumpInsn(Opcodes.IFEQ, elseLabel);
// then 分支
mv.visitLdcInsn("condition is true");
mv.visitInsn(Opcodes.ARETURN);
mv.visitJumpInsn(Opcodes.GOTO, endLabel);
// else 分支
mv.visitLabel(elseLabel);
mv.visitLdcInsn("condition is false");
mv.visitInsn(Opcodes.ARETURN);
// 结束
mv.visitLabel(endLabel);TraceClassVisitor
将字节码访问过程打印为文本,用于调试:
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.PrintWriter;
ClassReader cr = new ClassReader("com.example.MyClass");
ClassWriter cw = new ClassWriter(0);
TraceClassVisitor tcv = new TraceClassVisitor(cw, new PrintWriter(System.out));
cr.accept(tcv, 0);CheckClassAdapter
在开发时验证生成的字节码是否符合 JVM 规范:
import org.objectweb.asm.util.CheckClassAdapter;
ClassWriter cw = new ClassWriter(0);
CheckClassAdapter cca = new CheckClassAdapter(cw);
// 将 cca 作为 ClassVisitor 传入,ASM 会自动进行校验ByteBuddy 框架
ByteBuddy 是在 ASM 之上构建的高级字节码操作框架,提供了 Fluent API,大幅降低了字节码增强的复杂度。
ByteBuddy 基础
Maven 坐标
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.15</version>
</dependency>
<!-- 如需 Java Agent 支持 -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.14.15</version>
</dependency>Fluent API 风格
ByteBuddy 采用链式调用的 Fluent API,所有操作都从一个 ByteBuddy 实例开始:
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
// 动态创建类
Class<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello ByteBuddy!"))
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
System.out.println(dynamicType.newInstance().toString()); // 输出: Hello ByteBuddy!核心 API 组件
| 组件 | 说明 |
|---|---|
ByteBuddy | 入口类,配置默认设置 |
DynamicType | 动态类型的描述,通过 make() 生成 |
ElementMatcher | 匹配类/方法/字段的谓词 |
Implementation | 方法实现策略 |
AgentBuilder | Java Agent 构建器 |
动态创建类
subclass / redefine / reba se
ByteBuddy 支持三种类增强模式:
// 1. subclass - 创建子类(最常用)
Class<?> proxyClass = new ByteBuddy()
.subclass(Service.class)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(Interceptor.class))
.make()
.load(getClass().getClassLoader())
.getLoaded();
// 2. redefine - 直接修改目标类的字节码
Class<?> redefined = new ByteBuddy()
.redefine(Service.class)
.method(ElementMatchers.named("hello"))
.intercept(FixedValue.value("redefined!"))
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
// 3. rebind - 合并原方法逻辑(保留原方法为 private synthetic)
Class<?> rebased = new ByteBuddy()
.rebase(Service.class)
.method(ElementMatchers.named("hello"))
.intercept(FixedValue.value("intercepted!"))
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();定义名称、方法、字段和注解
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodDelegation;
// 完整定义一个新的类
Class<?> clazz = new ByteBuddy()
.subclass(Object.class)
.name("com.example.MyDynamicClass") // 指定类名
.defineField("counter", int.class, Visibility.PRIVATE) // 定义字段
.defineMethod("getCounter", int.class, Visibility.PUBLIC) // 定义方法
.intercept(FixedValue.value(42))
.defineMethod("hello", String.class, Visibility.PUBLIC)
.intercept(FixedValue.value("world"))
.annotateType(AnnotationDescription.Builder
.ofType(Deprecated.class)
.build()) // 添加类注解
.make()
.load(getClass().getClassLoader())
.getLoaded();方法委托
方法委托是 ByteBuddy 的核心功能,通过 MethodDelegation 可以将拦截的方法调用转发到指定的拦截器类。
// 目标类
public class UserService {
public String getUserName(long id) {
return "User-" + id;
}
}
// 拦截器
public class LogInterceptor {
public static String intercept(@Origin Method method,
@AllArguments Object[] args,
@SuperMethod Method superMethod,
@SuperCall Callable<?> callable) throws Exception {
System.out.println("[Before] " + method.getName());
try {
Object result = callable.call(); // 调用原方法
System.out.println("[After] " + method.getName() + " = " + result);
return (String) result;
} catch (Exception e) {
System.out.println("[Error] " + method.getName() + " - " + e.getMessage());
throw e;
}
}
}
// 应用增强
UserService proxy = new ByteBuddy()
.subclass(UserService.class)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(LogInterceptor.class))
.make()
.load(UserService.class.getClassLoader())
.getLoaded()
.getDeclaredConstructor()
.newInstance();
proxy.getUserName(1001L);
// 输出:
// [Before] getUserName
// [After] getUserName = User-1001委托注解说明
| 注解 | 说明 |
|---|---|
@Origin | 注入被拦截的 Method 对象或 Class 对象 |
@AllArguments | 注入被拦截方法的所有参数(Object[]) |
@SuperMethod | 注入原方法的 Method 对象 |
@SuperCall | 注入 Callable / Runnable,调用即执行原方法 |
@Super | 注入动态创建的 super 对象,可调用原方法 |
@DefaultCall | 调用默认方法实现(接口默认方法) |
@Pipe | 通过 Executor 风格的接口调用原方法 |
@Pipe 使用示例
import net.bytebuddy.implementation.bind.annotation.Pipe;
// 定义转发接口
interface Forwarder<T> {
T forward(Class<?> target, Object... args);
}
// 拦截器
public class PipeInterceptor {
public static String intercept(@Pipe Forwarder<String> forwarder) {
System.out.println("[Pipe Before]");
String result = forwarder.forward(UserService.class, 1001L);
System.out.println("[Pipe After]");
return result;
}
}动态拦截与 Java Agent
AgentBuilder
ByteBuddy 为 Java Agent 提供了 AgentBuilder API,用于在类加载时或运行时拦截类的加载。
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class MyAgent {
public static void premain(String args, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.nameStartsWith("com.example")) // 匹配目标类
.transform((builder, typeDescription, classLoader, module, loaded) ->
builder.method(ElementMatchers.any())
.intercept(Advice.to(MyAdvice.class)) // 使用 Advice 方式
)
.installOn(inst);
}
}Transform / BuildPlugin
// 自定义 Transformer
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, module, loaded) ->
builder.method(ElementMatchers.named("execute"))
.intercept(MethodDelegation.to(TimingInterceptor.class));Advice 方式(推荐)
Advice 是 ByteBuddy 推荐的轻量级拦截方式,无需学习委托注解即可实现方法前后增强:
import net.bytebuddy.asm.Advice;
public class TimingAdvice {
@Advice.OnMethodEnter
static long enter() {
return System.nanoTime();
}
@Advice.OnMethodExit(onThrowable = Throwable.class)
static void exit(@Advice.Enter long start,
@Advice.This Object self,
@Advice.Origin String method,
@Advice.Return(readOnly = false) Object result,
@Advice.Thrown Throwable thrown) {
long duration = System.nanoTime() - start;
System.out.println(method + " took " + duration + "ns");
if (thrown != null) {
System.out.println(method + " threw " + thrown.getClass().getSimpleName());
}
}
}Agent 启动方式
// premain - 在 JVM 启动时通过 -javaagent 参数加载
public class PremainAgent {
public static void premain(String args, Instrumentation inst) {
System.out.println("Premain Agent loaded with args: " + args);
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform(...)
.installOn(inst);
}
}
// agentmain - 在 JVM 运行时通过 Attach API 动态加载
public class AgentMainAgent {
public static void agentmain(String args, Instrumentation inst) {
System.out.println("Agentmain loaded at runtime with args: " + args);
// 需要对已加载的类进行 retransform
inst.addTransformer(new ClassFileTransformer() { ... }, true);
// 触发已加载类的转换
inst.retransformClasses(TargetClass.class);
}
}<!-- META-INF/MANIFEST.MF 配置 -->
Premain-Class: com.example.PremainAgent
Agent-Class: com.example.AgentMainAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true字节码增强实战
AOP 拦截器
方法耗时拦截器
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class TimingAgent {
public static void premain(String args, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.nameStartsWith("com.example.service"))
.transform((builder, td, cl, m, l) -> builder
.method(ElementMatchers.isPublic())
.intercept(Advice.to(TimingAdvice.class)))
.installOn(inst);
}
}
class TimingAdvice {
@Advice.OnMethodEnter
static long enter() {
return System.nanoTime();
}
@Advice.OnMethodExit
static void exit(@Advice.Enter long start,
@Advice.Origin("#m") String methodName) {
long cost = (System.nanoTime() - start) / 1_000_000;
if (cost > 100) { // 超过 100ms 输出慢调用警告
System.out.println("[SLOW] " + methodName + " cost " + cost + "ms");
}
}
}调用链路与 TraceId 注入
import net.bytebuddy.asm.Advice;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
import java.util.UUID;
public class TraceIdAgent {
// 使用 ThreadLocal 保存 TraceId
private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
public static void premain(String args, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.nameStartsWith("com.example.controller"))
.transform((builder, td, cl, m, l) -> builder
.method(ElementMatchers.any())
.intercept(Advice.to(TraceIdAdvice.class)))
.installOn(inst);
}
static class TraceIdAdvice {
@Advice.OnMethodEnter
static void enter(@Advice.Origin("#m") String method) {
if (TRACE_ID.get() == null) {
TRACE_ID.set(UUID.randomUUID().toString().replace("-", ""));
}
System.out.println("[TraceId: " + TRACE_ID.get() + "] Enter " + method);
}
@Advice.OnMethodExit
static void exit(@Advice.Origin("#m") String method) {
System.out.println("[TraceId: " + TRACE_ID.get() + "] Exit " + method);
}
}
// 对外提供 TraceId 获取方法(供业务代码调用)
public static String getTraceId() {
return TRACE_ID.get();
}
}异常记录拦截器
public class ExceptionRecorderAdvice {
@Advice.OnMethodExit(onThrowable = Throwable.class)
static void recordException(@Advice.Origin("#m") String method,
@Advice.Thrown Throwable thrown) {
if (thrown != null) {
System.err.println("[EXCEPTION] " + method + " - " + thrown.getClass().getName()
+ ": " + thrown.getMessage());
// 可在此处上报异常到监控系统(如 Sentry / Cat)
}
}
}动态代理
ByteBuddy 替代 JDK Proxy
JDK 动态代理要求目标类实现接口,且调用需要经过 InvocationHandler 的反射分发,性能较低。ByteBuddy 可以直接生成目标类的子类实现代理,无需接口。
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
// 目标类(无需接口)
public class OrderService {
public String createOrder(String userId, double amount) {
return "Order-" + userId + "-" + amount;
}
public final String getStatus(long orderId) {
return "PAID";
}
}
// 代理拦截器
public class OrderServiceInterceptor {
public static Object intercept(@SuperCall java.util.concurrent.Callable<?> callable,
@Origin Method method) throws Exception {
System.out.println("[Proxy] 调用方法: " + method.getName());
long start = System.nanoTime();
try {
Object result = callable.call();
long cost = (System.nanoTime() - start) / 1_000_000;
System.out.println("[Proxy] " + method.getName() + " 返回: " + result + " (耗时: " + cost + "ms)");
return result;
} catch (Exception e) {
System.out.println("[Proxy] " + method.getName() + " 异常: " + e.getMessage());
throw e;
}
}
}
// 创建代理
OrderService proxy = new ByteBuddy()
.subclass(OrderService.class)
.method(ElementMatchers.not(ElementMatchers.isFinal())) // 不代理 final 方法
.intercept(MethodDelegation.to(OrderServiceInterceptor.class))
.make()
.load(OrderService.class.getClassLoader())
.getLoaded()
.getDeclaredConstructor()
.newInstance();
String result = proxy.createOrder("user001", 99.99);
// 输出:
// [Proxy] 调用方法: createOrder
// [Proxy] createOrder 返回: Order-user001-99.99 (耗时: 0ms)动态接口实现
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.InvokeDynamic;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.FixedValue;
// 定义接口
public interface Cache {
String get(String key);
void put(String key, String value);
}
// 使用 ByteBuddy 动态实现接口
Class<? extends Cache> cacheClass = new ByteBuddy()
.implement(Cache.class)
.method(ElementMatchers.named("get"))
.intercept(FixedValue.value("default"))
.method(ElementMatchers.named("put"))
.intercept(InvokeDynamic.noArguments())
.make()
.load(Cache.class.getClassLoader())
.getLoaded()
.asSubclass(Cache.class);
Cache cache = cacheClass.getDeclaredConstructor().newInstance();
System.out.println(cache.get("key1")); // 输出: default代理类缓存
为避免重复生成代理类浪费内存和 CPU,应缓存生成的代理类:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ProxyCache {
private static final ConcurrentMap<Class<?>, Class<?>> PROXY_CACHE = new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> getProxyClass(Class<T> targetClass) {
return (Class<? extends T>) PROXY_CACHE.computeIfAbsent(targetClass, clazz -> {
try {
return new ByteBuddy()
.subclass(clazz)
.method(ElementMatchers.not(ElementMatchers.isFinal()))
.intercept(MethodDelegation.to(DefaultInterceptor.class))
.make()
.load(clazz.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
} catch (Exception e) {
throw new RuntimeException("创建代理类失败: " + clazz.getName(), e);
}
});
}
@SuppressWarnings("unchecked")
public static <T> T newProxyInstance(Class<T> targetClass, Object... constructorArgs) throws Exception {
Class<? extends T> proxyClass = getProxyClass(targetClass);
if (constructorArgs.length == 0) {
return proxyClass.getDeclaredConstructor().newInstance();
}
// 根据参数查找对应构造方法
Class<?>[] paramTypes = new Class<?>[constructorArgs.length];
for (int i = 0; i < constructorArgs.length; i++) {
paramTypes[i] = constructorArgs[i].getClass();
}
return proxyClass.getDeclaredConstructor(paramTypes).newInstance(constructorArgs);
}
}热更新
热更新允许在 JVM 运行时修改已加载类的行为,而无需重启应用。
Instrumentation 核心 API
| API | 说明 |
|---|---|
redefineClasses(ClassDefinition...) | 完全替换一个类的字节码,不触发类初始化 |
retransformClasses(Class<?>...) | 重新转换已加载的类,会再次调用已注册的 ClassFileTransformer |
addTransformer(ClassFileTransformer, boolean) | 注册类文件转换器,boolean 参数表示是否支持 retransform |
getAllLoadedClasses() | 获取当前 JVM 中所有已加载的类 |
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
// ClassFileTransformer 接口实现
public class SimpleTransformer implements ClassFileTransformer {
private final String targetClassName;
private final byte[] newClassBytes;
public SimpleTransformer(String targetClassName, byte[] newClassBytes) {
this.targetClassName = targetClassName.replace('.', '/');
this.newClassBytes = newClassBytes;
}
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
if (className.equals(targetClassName)) {
// 通过 ASM / ByteBuddy 修改字节码后返回新的 byte[]
return newClassBytes;
}
return null; // 返回 null 表示不修改
}
}redefineClasses 示例
public class RedefineExample {
public static void redefineClass(Instrumentation inst,
Class<?> targetClass,
byte[] newBytes) throws Exception {
ClassDefinition def = new ClassDefinition(targetClass, newBytes);
inst.redefineClasses(def);
System.out.println("重新定义类成功: " + targetClass.getName());
}
}retransformClasses 示例
public class RetransformExample {
public static void retransformClass(Instrumentation inst,
Class<?> targetClass,
ClassFileTransformer transformer) throws Exception {
// 注册 transformer(第二个参数 true 表示支持 retransform)
inst.addTransformer(transformer, true);
// 触发指定类的 retransform
inst.retransformClasses(targetClass);
// 若该 transformer 只用于本次,可以移除
inst.removeTransformer(transformer);
}
}Attach API
Attach API 允许从另一个 JVM 进程向目标 JVM 动态附加 Agent,是实现热更新的关键。
import com.sun.tools.attach.VirtualMachine;
public class HotAttacher {
public static void attachToJVM(String pid, String agentJarPath) throws Exception {
VirtualMachine vm = VirtualMachine.attach(pid);
try {
vm.loadAgent(agentJarPath, "arg1=value1");
System.out.println("Agent 加载成功,目标 PID: " + pid);
} finally {
vm.detach();
}
}
// 使用示例
public static void main(String[] args) throws Exception {
// 获取目标 JVM 的 PID(可通过 jps 命令查看)
String pid = "12345";
String agentJar = "/path/to/my-agent.jar";
attachToJVM(pid, agentJar);
}
}<!-- 在 pom.xml 中配置 Attach API 依赖 -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>ASM vs ByteBuddy 对比
综合对比
| 维度 | ASM | ByteBuddy |
|---|---|---|
| API 复杂度 | 底层 API,需理解字节码指令集和栈帧模型 | 高层 Fluent API,无需掌握字节码细节 |
| 学习曲线 | 陡峭(数周至数月) | 平缓(数天至一周) |
| 代码量 | 较多,同样的功能需要更多代码 | 较少,链式调用简洁 |
| 性能 | 最高,直接操作字节码,零额外开销 | 较高,底层基于 ASM,增加少量抽象层开销 |
| 启动生成开销 | 低 | 略高于 ASM |
| 功能丰富度 | 基础字节码读写,需自行实现高级功能 | 内置 MethodDelegation、Advice、AgentBuilder 等 |
| 代理机制 | 需手动实现 ClassVisitor/MethodVisitor | 支持 subclass/redefine/rebase 三种模式 |
| 调试支持 | TraceClassVisitor、CheckClassAdapter | 内置 TypeValidation、ErrorHandler |
| 社区活跃度 | 活跃,是底层标准库(Spring、Hibernate 等使用) | 活跃,增长迅速,被 Spring Boot 3+、Mockito、Hibernate 等采用 |
| 版本迭代 | 稳定,变化较小 | 较活跃,不断加入新特性 |
| Maven 坐标 | org.ow2.asm:asm:9.7 | net.bytebuddy:byte-buddy:1.14.15 |
| 模块化 | 多模块(asm/asm-commons/asm-tree/asm-util) | 双模块(byte-buddy + byte-buddy-agent) |
Maven 坐标对比
<!-- ASM 核心库 -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7</version>
</dependency>
<!-- ASM 工具库(调试/校验) -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>9.7</version>
</dependency>
<!-- ASM 树模型 API(另一种编程模型) -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>9.7</version>
</dependency>
<!-- ASM 注解支持 -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>9.7</version>
</dependency><!-- ByteBuddy 核心库 -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.15</version>
</dependency>
<!-- ByteBuddy Agent 支持(Instrumentation/Attach) -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.14.15</version>
</dependency>选型建议
选择 ASM 的场景
- 需要极致的运行时性能(如高性能 RPC 框架、序列化框架)
- 需要精确控制生成的字节码(如编译器、语言工具链)
- 框架底层集成,已经对字节码有深入理解
- 对依赖体积有严格要求(ASM 约 300KB,ByteBuddy 约 4MB)
- 学习目的,深入理解 JVM 字节码机制
选择 ByteBuddy 的场景
- 日常项目中的 AOP 拦截、方法增强
- 快速实现动态代理替代 JDK Proxy / CGLIB
- 需要通过 Java Agent 实现无侵入的监控、链路追踪
- 团队不具备字节码专业知识
- 需要频繁迭代的字节码增强逻辑
组合使用
在实际项目中,ByteBuddy 内部已经使用 ASM 作为字节码生成引擎,因此两者并非互斥。通常情况下推荐优先使用 ByteBuddy,当 ByteBuddy 无法满足底层控制需求时,再回退到 ASM 进行定制化开发。
选型决策流程:
需要字节码增强?
|
+-----+------+
| |
需要极致性能? 常规 AOP/代理?
| |
+---+---+ +---+---+
| | | |
ASM ByteBuddy 团队熟悉 ASM?
|
+-----+-----+
| |
ByteBuddy ASM主流框架使用情况
| 框架 | 使用的字节码技术 | 说明 |
|---|---|---|
| Spring AOP (CGLIB) | ASM | Spring 5 以前使用 CGLIB(底层 ASM),Spring 6 直接使用 ByteBuddy |
| Spring Boot 3+ | ByteBuddy | 使用 ByteBuddy 实现 AOP 代理和上下文增强 |
| Hibernate | ASM (ByteBuddy) | Hibernate 6 开始从 Javassist 切换为 ByteBuddy |
| Mockito | ByteBuddy | Mockito 使用 ByteBuddy 生成 mock 对象 |
| Jackson | ASM | 使用 ASM 生成高性能的序列化器 |
| Netty | ASM | 使用 ASM 实现无反射的编解码器 |
| Apache Dubbo | ASM / ByteBuddy | 从 ASM 逐步过渡到 ByteBuddy |
| SkyWalking | ASM / ByteBuddy | 插件化增强,同时使用两者 |
| Arthas | ASM | 使用 ASM 实现运行时诊断命令 |