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)