一、Java 字节码:JVM 的“汇编语言”
Java 源码(.java
) → javac
编译 → 字节码(.class
) → JVM 执行
字节码是一种基于栈的指令集,每条指令对应一个操作。例如:
int a = 10;
int b = 20;
int c = a + b;
对应的字节码(通过 javap -c
查看):
iconst_10 // 将常量 10 压入操作数栈
istore_1 // 将栈顶值弹出,存入局部变量表 slot 1 (a)
iconst_20 // 将常量 20 压入栈
istore_2 // 存入 slot 2 (b)
iload_1 // 将 a 的值压入栈
iload_2 // 将 b 的值压入栈
iadd // 弹出两个值,相加,结果压入栈
istore_3 // 存入 slot 3 (c)
理解字节码是进行字节码增强的基础。
二、ASM 框架:字节码操作的“瑞士军刀”
ASM 是一个轻量级、高性能的 Java 字节码操作和分析框架。它可以直接以二进制形式读写 .class
文件,支持:
- Core API:基于事件驱动的访问者模式(Visitor Pattern)
- Tree API:将类结构构建成树形,便于修改
我们主要使用 Core API,因为它更高效。
ASM 核心组件
ClassReader
:读取.class
文件ClassVisitor
:访问类结构(类名、方法、字段等)MethodVisitor
:访问方法字节码ClassWriter
:生成修改后的字节码
三、动手实战:手写一个方法耗时监控框架
目标:实现一个 @LogExecutionTime
注解,被标注的方法执行时,自动打印其耗时。
1. 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
2. 编写 ASM 字节码增强逻辑
我们需要在目标方法的开头插入 System.nanoTime()
,在结尾插入计算并打印耗时的代码。
public class TimingMethodVisitor extends MethodVisitor {
private String methodName;
private String owner; // 类名
public TimingMethodVisitor(MethodVisitor mv, String methodName, String owner) {
super(Opcodes.ASM9, mv);
this.methodName = methodName;
this.owner = owner;
}
@Override
public void visitCode() {
// 在方法开头插入:long start = System.nanoTime();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
mv.visitVarInsn(Opcodes.LSTORE, 1); // 存入局部变量 slot 1
super.visitCode();
}
@Override
public void visitInsn(int opcode) {
// 在方法正常返回前插入耗时计算
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
// long end = System.nanoTime();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
mv.visitVarInsn(Opcodes.LSTORE, 3); // 存入 slot 3
// System.out.println("Method X took: " + (end - start) + " 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("Method " + methodName + " took: ");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(Opcodes.LLOAD, 3); // end
mv.visitVarInsn(Opcodes.LLOAD, 1); // start
mv.visitInsn(Opcodes.LSUB); // end - start
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);
}
mv.visitInsn(opcode);
}
}
3. 类访问器(ClassVisitor)
public class TimingClassVisitor extends ClassVisitor {
private String className;
public TimingClassVisitor(ClassVisitor cv, String className) {
super(Opcodes.ASM9, cv);
this.className = className;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
if (mv != null) {
// 检查方法是否有 @LogExecutionTime 注解
mv = new MethodVisitor(Opcodes.ASM9, mv) {
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
AnnotationVisitor av = super.visitAnnotation(desc, visible);
if ("Lcom/example/LogExecutionTime;".equals(desc)) {
// 有注解,返回我们自定义的 MethodVisitor 进行增强
return new TimingMethodVisitor(mv, name, className);
}
return av;
}
};
}
return mv;
}
}
4. Java Agent 实现(在类加载时增强)
创建 MANIFEST.MF
:
Premain-Class: com.example.TimingAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent 主类:
public class TimingAgent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
// 跳过 JDK 和 ASM 自身
if (className.startsWith("java/") ||
className.startsWith("sun/") ||
className.equals("com/example/TimingClassVisitor")) {
return null;
}
try {
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new TimingClassVisitor(cw, className.replace('/', '.'));
cr.accept(cv, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
});
}
}
四、测试验证
public class TestService {
@LogExecutionTime
public void businessMethod() throws InterruptedException {
Thread.sleep(100); // 模拟业务逻辑
System.out.println("执行核心业务...");
}
}
// 主程序
public class Main {
public static void main(String[] args) {
new TestService().businessMethod();
}
}
运行结果:
执行核心业务...
Method businessMethod took: 100001234 ns
✅ 成功!我们实现了无侵入的方法耗时监控。