ASM字节码插桩:方法添加TryCatch捕获异常并打印

1. 预期目标

  • 使用ASM为方法添加try-catch代码块
  • 比如有一个Hello类
package org.example.asm8.trycatch;

public class Hello {

    public void compute(String name, int age) {
        int length = name.length();
        System.out.println(name + " length = " + length);
        int div = div(age);
        System.out.println("996 / " + age + " = " + div);
    }

    private int div(int age) {
        return 996 / age;
    }

}

  • 进行字节码插桩后达到下面效果
package org.example.asm8.trycatch;

public class Hello {

    public void compute(String name, int age) {
        try {
            int length = name.length();
            System.out.println(name + " length = " + length);
            int div = this.div(age);
            System.out.println("996 / " + age + " = " + div);
        } catch (Exception var6) {
            PrintUtils.printException("compute", var6);
            throw var6;
        }
    }

    private int div(int age) {
        try {
            return 996 / age;
        } catch (Exception var3) {
            PrintUtils.printException("div", var3);
            throw var3;
        }
    }
}

2. 代码实现

2.1 打印异常类

package org.example.asm8.trycatch;

public class PrintUtils {

    public static void printException(String methodName, Exception exception) {
        System.out.println("监控 -> [方法名:" + methodName + ",异常信息:" + exception.getMessage() + "]");
    }

}

2.2 字节码增强类

package org.example.asm8.trycatch;

import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

public class AddTryCatch implements Opcodes {

    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader(Hello.class.getName());
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
        cr.accept(new MyClassVisitor(ASM9, cw), ClassReader.EXPAND_FRAMES);
        byte[] bytes = cw.toByteArray();
        // 生成class
        String path = AddTryCatch.class.getResource("/").getPath() + "org/example/asm8/trycatch/Hello.class";
        System.out.println("输出路径:" + path);
        try (FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(bytes);
        }
    }

    static class MyClassVisitor extends ClassVisitor {

        protected MyClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
            String[] exceptions) {
            MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
            if (methodVisitor != null) {
                // 排除构造方法、抽象方法和本地方法
                boolean isConstructor = "<init>".equals(name);
                boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
                boolean isNativeMethod = (access & ACC_NATIVE) != 0;
                if (!isConstructor && !isAbstractMethod && !isNativeMethod) {
                    methodVisitor = new MyMethodVisitor(api, methodVisitor, access, name, descriptor);
                }
            }
            return methodVisitor;
        }
    }

    static class MyMethodVisitor extends AdviceAdapter {
        int localIndex;
        private final Label startLabel = new Label();
        private final Label endLabel = new Label();
        private final Label handlerLabel = new Label();

        protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
        }

        @Override
        protected void onMethodEnter() {
            // 标记try开始位置
            super.visitLabel(startLabel);
            // 调用父类方法实现
            super.onMethodEnter();
        }

        // 在方法结束前,用于添加 Catch 块
        @Override
        public void visitMaxs(int maxStack, int maxLocals) {
            // 标记try结束位置
            super.visitLabel(endLabel);
            // 添加catch内处理逻辑
            super.visitLabel(handlerLabel);
            // 拿到局部变量表中空闲位置的索引,用于后面添加数据到局部变量表中
            localIndex = nextLocal;
            // 将异常信息exception添加到局部变量表localIndex位置
            super.visitVarInsn(ASTORE, localIndex);
            printException();

            // 将局部变量表中localIndex位置的数据再次压入操作数栈顶,用于抛出
            super.visitVarInsn(ALOAD, localIndex);
            // 将操作数栈顶的数据抛出
            super.visitInsn(ATHROW);

            // 访问try catch块
            super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, "java/lang/Exception");

            // 调用父类方法实现
            super.visitMaxs(maxStack, maxLocals);
        }

        private void printException() {
            // 方法名压入栈顶
            super.visitLdcInsn(getName());
            // 将局部变量表中localIndex位置的数据压入操作数栈顶
            super.visitVarInsn(ALOAD, localIndex);
            // 调用打印类
            super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(PrintUtils.class), "printException",
                "(Ljava/lang/String;Ljava/lang/Exception;)V", false);
        }

    }

}

2.3 执行增强

  • 执行main方法,查看被修改后的Hello.class,可以看出已经在方法中添加了try-catch和exception的信息打印

在这里插入图片描述

2.4 验证

  • 测试类
package org.example.asm8.trycatch;

public class HelloRun {

    public static void main(String[] args) {
        Hello hello = new Hello();
        hello.compute("Fisher", 0);
    }

}

  • 打印结果
Fisher length = 6
监控 -> [方法名:div,异常信息:/ by zero]
监控 -> [方法名:compute,异常信息:/ by zero]
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at org.example.asm8.trycatch.Hello.div(Hello.java:13)
	at org.example.asm8.trycatch.Hello.compute(Hello.java:8)
	at org.example.asm8.trycatch.HelloRun.main(HelloRun.java:7)

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值