适合人群: 框架开发者、AOP爱好者
难度等级: ⭐⭐⭐⭐⭐ (高级)
阅读时间: 15分钟
📖 什么是字节码增强?
字节码增强 = 在不修改源代码的情况下,修改class文件
应用场景:
- AOP(Spring AOP)
- 性能监控(Skywalking)
- 热部署(JRebel)
- 动态代理(Mybatis)
- Mock框架(Mockito)
🛠️ 三大工具对比
ASM - 最底层,最强大 ⚡
// 直接操作字节码指令
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_8, ACC_PUBLIC, "com/example/Hello", null, "java/lang/Object", null);
优点:
✅ 性能最快
✅ 功能最强大
✅ Spring、CGLIB都用它
缺点:
❌ 学习成本高
❌ 需要了解JVM指令
❌ 代码复杂
使用场景: 框架底层、性能要求极高
Javassist - 最简单,最易用 😊
// 用Java语法操作
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.example.Hello");
CtMethod m = cc.getDeclaredMethod("sayHello");
m.insertBefore("{ System.out.println(\"Before\"); }");
优点:
✅ API简单
✅ 用Java语法,不需要懂字节码
✅ 学习成本低
缺点:
❌ 性能稍慢
❌ 功能受限
使用场景: 快速开发、简单AOP、字节码入门
ByteBuddy - 最现代,最优雅 🎨
// 流式API,类型安全
Class<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.load(getClass().getClassLoader())
.getLoaded();
优点:
✅ API优雅
✅ 类型安全
✅ Mockito、Hibernate都在用
缺点:
❌ JAR包较大
使用场景: 现代框架、Mock、动态代理
🎯 实战案例:方法耗时统计
需求
// 原始代码
public class UserService {
public void saveUser(User user) {
// 业务逻辑
}
}
// 增强后自动打印耗时
// [saveUser] 执行耗时: 123ms
ASM实现
public class TimingMethodVisitor extends MethodVisitor {
@Override
public void visitCode() {
super.visitCode();
// 方法开始时记录时间
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 1);
}
@Override
public void visitInsn(int opcode) {
if (opcode >= IRETURN && opcode <= RETURN) {
// 方法返回前计算耗时
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, 1);
mv.visitInsn(LSUB);
// 打印耗时
}
super.visitInsn(opcode);
}
}
Javassist实现
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.example.UserService");
CtMethod method = cc.getDeclaredMethod("saveUser");
method.addLocalVariable("startTime", CtClass.longType);
method.insertBefore("startTime = System.currentTimeMillis();");
method.insertAfter("{" +
"long endTime = System.currentTimeMillis();" +
"System.out.println(\"耗时: \" + (endTime - startTime) + \"ms\");" +
"}");
cc.writeFile();
ByteBuddy实现
new ByteBuddy()
.redefine(UserService.class)
.method(named("saveUser"))
.intercept(MethodDelegation.to(TimingInterceptor.class))
.make()
.load(ClassLoader.getSystemClassLoader());
public class TimingInterceptor {
@RuntimeType
public static Object intercept(@SuperCall Callable<?> zuper) {
long start = System.currentTimeMillis();
try {
return zuper.call();
} finally {
System.out.println("耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
}
📊 性能对比
┌─────────────┬──────┬────────┬────────┐
│ 工具 │性能 │易用性 │推荐度 │
├─────────────┼──────┼────────┼────────┤
│ ASM │⭐⭐⭐⭐⭐│⭐⭐ │框架底层│
│ Javassist │⭐⭐⭐ │⭐⭐⭐⭐⭐│快速开发│
│ ByteBuddy │⭐⭐⭐⭐│⭐⭐⭐⭐ │现代首选│
└─────────────┴──────┴────────┴────────┘
💡 总结
选择原则:
- 性能要求极高 → ASM
- 快速开发、简单场景 → Javassist
- 现代项目、追求优雅 → ByteBuddy ✅
记住:不要重复造轮子,优先使用现有框架(Spring AOP)!
下一篇: 方法区(元空间)溢出问题