0
点赞
收藏
分享

微信扫一扫

手写一个 ASM 字节码增强框架

Star英 13小时前 阅读 0

一、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

✅ 成功!我们实现了无侵入的方法耗时监控。

举报

相关推荐

0 条评论