Java ASM系列((066)Exception处理)

白日放歌须纵酒,青春作伴好还乡。这篇文章主要讲述Java ASM系列:(066)Exception处理相关的知识,希望能为你提供帮助。
本文属于Java ASM系列二:OPCODE当中的一篇。
对于《java ASM系列二:OPCODE》有配套的视频讲解,可以点击这里和这里进行查看;同时,也可以点击这里查看源码资料。
当方法内部的instructions执行的时候,可能会遇到异常情况,那么JVM会将其封装成一个具体的Exception子类的对象,然后把这个“异常对象”放在operand stack上,接下来要怎么处理呢?
对于operand stack上的“异常对象”,有两种处理情况:

  • 第一种,当前方法处理不了,只能再交给别的方法进行处理
  • 第二种,当前方法能够处理,就按照自己的处理逻辑来进行处理了
在本文当中,我们就只关注第二种情况,也就是当前方法能够这个“异常对象”。
在当前方法中,处理异常的机制,在Code属性结构当中,分成了两个部分进行存储:
  • 第一部分,是位于exception_table[]内,它类似于中国古代打仗当中“军师”(运筹帷幄),它会告诉我们应该怎么处理这个“异常对象”。
  • 第二部分,是位于code[]内,它类似于中国古代打仗当中的“将军和士兵”(决胜千里),它是具体的来执行对“异常对象”处理的操作。
Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; {u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }

1. 异常处理的顺序 1.1. 代码举例
假如有一个HelloWorld类,代码如下:
public class HelloWorld { public void test(int a, int b) { try { int val = a / b; System.out.println(val); } catch (ArithmeticException ex1) { System.out.println("catch ArithmeticException"); } catch (Exception ex2) { System.out.println("catch Exception"); } } }

接着,我们来写一个HelloWorldRun类,来对HelloWorld.test()方法进行调用。
import sample.HelloWorld; public class HelloWorldRun { public static void main(String[] args) throws Exception { HelloWorld instance = new HelloWorld(); instance.test(10, 0); } }

我们来运行一下HelloWorldRun类,查看一下输出结果:
catch ArithmeticException

1.2. exception table结构
那么,异常处理的逻辑是存储在哪里呢?
从ClassFile的角度来看,异常处理的逻辑,是存在Code属性的exception_table部分。
Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; {u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }

接着,我们对HelloWorld.class文件进行分析,其test()方法的Code结构当中各个条目具体取值如下:
Code_attribute { attribute_name_index=\'000D\' (#13) attribute_length=\'000000C1\' (193) max_stack=\'0002\' (2) max_locals=\'0004\' (4) code_length=\'00000024\' (36) code: 1B1C6C3EB200021DB60003A700184EB200021205B60006A7000C4EB200021208B60006B1 exception_table_length=\'0002\' (2)// 注意,这里的数量是2 exception_table[0] {// 这里是第1个异常处理 start_pc=\'0000\' (0) end_pc=\'000B\' (11) handler_pc=\'000E\' (14) catch_type=\'0004\' (#4)// 这里表示捕获的异常是ArithmeticException } exception_table[1] {// 这里是第2个异常处理 start_pc=\'0000\' (0) end_pc=\'000B\' (11) handler_pc=\'001A\' (26) catch_type=\'0007\' (#7)// 这里表示捕获的异常是Exception类型 } attributes_count=\'0003\' (3) LineNumberTable: 000E00000026... LocalVariableTable: 000F0000003E... StackMapTable: 001C0000000B... }

再接下来,我们结合着javap指令来理解一下exception table的含义:
$ javap -c sample.HelloWorld Compiled from "HelloWorld.java" public class sample.HelloWorld { ... public void test(int, int); Code: 0: iload_1 1: iload_2 2: idiv 3: istore_3 4: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream; 7: iload_3 8: invokevirtual #3// Method java/io/PrintStream.println:(I)V 11: goto35 14: astore_3 15: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream; 18: ldc#5// String catch ArithmeticException 20: invokevirtual #6// Method java/io/PrintStream.println:(Ljava/lang/String; )V 23: goto35 26: astore_3 27: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream; 30: ldc#8// String catch Exception 32: invokevirtual #6// Method java/io/PrintStream.println:(Ljava/lang/String; )V 35: return Exception table: fromtotarget type 01114Class java/lang/ArithmeticException 01126Class java/lang/Exception }

1.3. 调换exception table顺序
在这里,我们想调换一下exception table顺序:先捕获Exception异常,再捕获ArithmeticException异常。
我们想通过Java语言来实现调换一下exception table顺序,会发现代码会报错:
public class HelloWorld { public void test(int a, int b) { try { int val = a / b; System.out.println(val); } catch (Exception ex2) { System.out.println("catch Exception"); } catch (ArithmeticException ex1) { // Exception \'java.lang.ArithmeticException\' has already been caught System.out.println("catch ArithmeticException"); } } }

那么,应该怎么做呢?我们就通过ASM来帮助我们实现调换一下exception table顺序。
首先,我们来看一下,原本HelloWorld类的代码如何通过ASM代码来进行实现:
import lsieun.utils.FileUtils; import org.objectweb.asm.*; import static org.objectweb.asm.Opcodes.*; public class HelloWorldGenerateCore { public static void main(String[] args) throws Exception { String relative_path = "sample/HelloWorld.class"; String filepath = FileUtils.getFilePath(relative_path); // (1) 生成byte[]内容 byte[] bytes = dump(); // (2) 保存byte[]到文件 FileUtils.writeBytes(filepath, bytes); }public static byte[] dump() throws Exception { // (1) 创建ClassWriter对象 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); // (2) 调用visitXxx()方法 cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null); { MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "< init> ", "()V", null, null); mv1.visitCode(); mv1.visitVarInsn(ALOAD, 0); mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "< init> ", "()V", false); mv1.visitInsn(RETURN); mv1.visitMaxs(1, 1); mv1.visitEnd(); }{ MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(II)V", null, null); Label startLabel = new Label(); Label endLabel = new Label(); Label exceptionHandler01 = new Label(); Label exceptionHandler02 = new Label(); Label returnLabel = new Label(); mv2.visitCode(); mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandler01, "java/lang/ArithmeticException"); mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandler02, "java/lang/Exception"); mv2.visitLabel(startLabel); mv2.visitVarInsn(ILOAD, 1); mv2.visitVarInsn(ILOAD, 2); mv2.visitInsn(IDIV); mv2.visitVarInsn(ISTORE, 3); mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream; "); mv2.visitVarInsn(ILOAD, 3); mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); mv2.visitLabel(endLabel); mv2.visitJumpInsn(GOTO, returnLabel); mv2.visitLabel(exceptionHandler01); mv2.visitVarInsn(ASTORE, 3); mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream; "); mv2.visitLdcInsn("catch ArithmeticException"); mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String; )V", false); mv2.visitJumpInsn(GOTO, returnLabel); mv2.visitLabel(exceptionHandler02); mv2.visitVarInsn(ASTORE, 3); mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream; "); mv2.visitLdcInsn("catch Exception"); mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String; )V", false); mv2.visitLabel(returnLabel); mv2.visitInsn(RETURN); mv2.visitMaxs(2, 4); mv2.visitEnd(); } cw.visitEnd(); // (3) 调用toByteArray()方法 return cw.toByteArray(); } }

接下来,我们调换一下上述ASM代码中两个MethodVisitor.visitTryCatchBlock()方法调用的顺序:
mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandler02, "java/lang/Exception"); mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandler01, "java/lang/ArithmeticException");

那么,我们执行一下HelloWorldGenerateCore类,就可以生成HelloWorld.class文件。然后,我们执行一下javap -c sample.HelloWorld命令,来验证一下:
$javap -c sample.HelloWorld public class sample.HelloWorld { ... public void test(int, int); Code: 0: iload_1 1: iload_2 2: idiv 3: istore_3 4: getstatic#20// Field java/lang/System.out:Ljava/io/PrintStream; 7: iload_3 8: invokevirtual #26// Method java/io/PrintStream.println:(I)V 11: goto35 14: astore_3 15: getstatic#20// Field java/lang/System.out:Ljava/io/PrintStream; 18: ldc#28// String catch ArithmeticException 20: invokevirtual #31// Method java/io/PrintStream.println:(Ljava/lang/String; )V 23: goto35 26: astore_3 27: getstatic#20// Field java/lang/System.out:Ljava/io/PrintStream; 30: ldc#33// String catch Exception 32: invokevirtual #31// Method java/io/PrintStream.println:(Ljava/lang/String; )V 35: return Exception table: fromtotarget type 01126Class java/lang/Exception 01114Class java/lang/ArithmeticException }

再接着,我们来运行一下HelloWorldRun类,查看一下输出结果:
catch Exception

这样的一个输出结果,说明:exception table就是根据先后顺序来判定的,而不是找到最匹配的异常类型。
2. 为整个方法添加异常处理的逻辑 2.1. 代码举例
假如有一个HelloWorld类,代码如下:
public class HelloWorld { public void test(String name, int age) { try { int length = name.length(); System.out.println("length = " + length); } catch (NullPointerException ex) { System.out.println("name is null"); }int val = div(10, age); System.out.println("val = " + val); }public int div(int a, int b) { return a / b; } }

我们想实现的预期目标:将整个test()方法添加一个try-catch语句。
首先,我们来运行一下HelloWorldRun类:
import sample.HelloWorld; public class HelloWorldRun { public static void main(String[] args) throws Exception { HelloWorld instance = new HelloWorld(); instance.test(null, 0); } }

其输出结果如下:
name is null Exception in thread "main" java.lang.ArithmeticException: / by zero at sample.HelloWorld.div(HelloWorld.java:18) at sample.HelloWorld.test(HelloWorld.java:13) at run.HelloWorldRun.main(HelloWorldRun.java:8)

使用javap命令查看exception table的内容:
$javap -c sample.HelloWorld Compiled from "HelloWorld.java" public class sample.HelloWorld { ... public void test(java.lang.String, int); Code: 0: aload_1 1: invokevirtual #2// Method java/lang/String.length:()I 4: istore_3 5: getstatic#3// Field java/lang/System.out:Ljava/io/PrintStream; 8: new#4// class java/lang/StringBuilder 11: dup 12: invokespecial #5// Method java/lang/StringBuilder."< init> ":()V 15: ldc#6// String length = 17: invokevirtual #7// Method java/lang/StringBuilder.append:(Ljava/lang/String; )Ljava/lang/StringBuilder; 20: iload_3 21: invokevirtual #8// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 24: invokevirtual #9// Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: invokevirtual #10// Method java/io/PrintStream.println:(Ljava/lang/String; )V 30: goto42 33: astore_3 34: getstatic#3// Field java/lang/System.out:Ljava/io/PrintStream; 37: ldc#12// String name is null 39: invokevirtual #10// Method java/io/PrintStream.println:(Ljava/lang/String; )V 42: aload_0 43: bipush10 45: iload_2 46: invokevirtual #13// Method div:(II)I 49: istore_3 50: getstatic#3// Field java/lang/System.out:Ljava/io/PrintStream; 53: new#4// class java/lang/StringBuilder 56: dup 57: invokespecial #5// Method java/lang/StringBuilder."< init> ":()V 60: ldc#14// String val = 62: invokevirtual #7// Method java/lang/StringBuilder.append:(Ljava/lang/String; )Ljava/lang/StringBuilder; 65: iload_3 66: invokevirtual #8// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 69: invokevirtual #9// Method java/lang/StringBuilder.toString:()Ljava/lang/String; 72: invokevirtual #10// Method java/io/PrintStream.println:(Ljava/lang/String; )V 75: return Exception table: fromtotarget type 03033Class java/lang/NullPointerException... }

2.2. 正确的异常捕获处理
import org.objectweb.asm.*; import static org.objectweb.asm.Opcodes.*; public class MethodWithWholeTryCatchVisitor extends ClassVisitor { private final String methodName; private final String methodDesc; public MethodWithWholeTryCatchVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) { super(api, classVisitor); this.methodName = methodName; this.methodDesc = methodDesc; }@Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); if (mv != null & & !"< init> ".equals(name) & & methodName.equals(name) & & methodDesc.equals(descriptor)) { boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0; boolean isNativeMethod = (access & ACC_NATIVE) != 0; if (!isAbstractMethod & & !isNativeMethod) { mv = new MethodWithWholeTryCatchAdapter(api, mv, access, descriptor); } } return mv; }private static class MethodWithWholeTryCatchAdapter extends MethodVisitor { private final int methodAccess; private final String methodDesc; private final Label startLabel = new Label(); private final Label endLabel = new Label(); private final Label handlerLabel = new Label(); public MethodWithWholeTryCatchAdapter(int api, MethodVisitor methodVisitor, int methodAccess, String methodDesc) { super(api, methodVisitor); this.methodAccess = methodAccess; this.methodDesc = methodDesc; }public void visitCode() { // 首先,处理自己的代码逻辑 // (1) startLabel super.visitLabel(startLabel); // 其次,调用父类的方法实现 super.visitCode(); }@Override public void visitMaxs(int maxStack, int maxLocals) { // 首先,处理自己的代码逻辑 // (2) endLabel super.visitLabel(endLabel); // (3) handlerLabel super.visitLabel(handlerLabel); int localIndex = getLocalIndex(); super.visitVarInsn(ASTORE, localIndex); super.visitVarInsn(ALOAD, localIndex); super.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream; "); super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "(Ljava/io/PrintStream; )V", false); super.visitVarInsn(ALOAD, localIndex); super.visitInsn(Opcodes.ATHROW); // (4) visitTryCatchBlock super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, "java/lang/Exception"); // 其次,调用父类的方法实现 super.visitMaxs(maxStack, maxLocals); }private int getLocalIndex() { Type t = Type.getType(methodDesc); Type[] argumentTypes = t.getArgumentTypes(); boolean isStaticMethod = ((methodAccess & ACC_STATIC) != 0); int localIndex = isStaticMethod ? 0 : 1; for (Type argType : argumentTypes) { localIndex += argType.getSize(); } return localIndex; } } }

【Java ASM系列((066)Exception处理)】进行转换:
import lsieun.utils.FileUtils; import org.objectweb.asm.*; public class HelloWorldTransformCore { public static void main(String[] args) { String relative_path = "sample/HelloWorld.class"; String filepath = FileUtils.getFilePath(relative_path); byte[] bytes1 = FileUtils.readBytes(filepath); //(1)构建ClassReader ClassReader cr = new ClassReader(bytes1); //(2)构建ClassWriter ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); //(3)串连ClassVisitor int api = Opcodes.ASM9; ClassVisitor cv = new MethodWithWholeTryCatchVisitor(api, cw, "test", "(Ljava/lang/String; I)V"); //(4)结合ClassReader和ClassVisitor int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; cr.accept(cv, parsingOptions); //(5)生成byte[] byte[] bytes2 = cw.toByteArray(); FileUtils.writeBytes(filepath, bytes2); } }

转换完成之后,再次运行HelloWorldRun类,得到如下输出内容:
name is null java.lang.ArithmeticException: / by zero at sample.HelloWorld.div(Unknown Source) at sample.HelloWorld.test(Unknown Source) at run.HelloWorldRun.main(HelloWorldRun.java:8) Exception in thread "main" java.lang.ArithmeticException: / by zero at sample.HelloWorld.div(Unknown Source) at sample.HelloWorld.test(Unknown Source) at run.HelloWorldRun.main(HelloWorldRun.java:8)

使用javap命令查看exception table的内容:
$ javap -c sample.HelloWorld public class sample.HelloWorld { ... public void test(java.lang.String, int); Code: 0: aload_1 1: invokevirtual #18// Method java/lang/String.length:()I 4: istore_3 5: getstatic#24// Field java/lang/System.out:Ljava/io/PrintStream; 8: new#26// class java/lang/StringBuilder 11: dup 12: invokespecial #27// Method java/lang/StringBuilder."< init> ":()V 15: ldc#29// String length = 17: invokevirtual #33// Method java/lang/StringBuilder.append:(Ljava/lang/String; )Ljava/lang/StringBuilder; 20: iload_3 21: invokevirtual #36// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 24: invokevirtual #40// Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: invokevirtual #46// Method java/io/PrintStream.println:(Ljava/lang/String; )V 30: goto42 33: astore_3 34: getstatic#24// Field java/lang/System.out:Ljava/io/PrintStream; 37: ldc#48// String name is null 39: invokevirtual #46// Method java/io/PrintStream.println:(Ljava/lang/String; )V 42: aload_0 43: bipush10 45: iload_2 46: invokevirtual #52// Method div:(II)I 49: istore_3 50: getstatic#24// Field java/lang/System.out:Ljava/io/PrintStream; 53: new#26// class java/lang/StringBuilder 56: dup 57: invokespecial #27// Method java/lang/StringBuilder."< init> ":()V 60: ldc#54// String val = 62: invokevirtual #33// Method java/lang/StringBuilder.append:(Ljava/lang/String; )Ljava/lang/StringBuilder; 65: iload_3 66: invokevirtual #36// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 69: invokevirtual #40// Method java/lang/StringBuilder.toString:()Ljava/lang/String; 72: invokevirtual #46// Method java/io/PrintStream.println:(Ljava/lang/String; )V 75: return 76: astore_3 77: aload_3 78: getstatic#24// Field java/lang/System.out:Ljava/io/PrintStream; 81: invokevirtual #60// Method java/lang/Exception.printStackTrace:(Ljava/io/PrintStream; )V 84: aload_3 85: athrow Exception table: fromtotarget type 03033Class java/lang/NullPointerException 07676Class java/lang/Exception... }

2.3. 错误的异常捕获处理
import org.objectweb.asm.*; import static org.objectweb.asm.Opcodes.*; public class MethodWithWholeTryCatchVisitor extends ClassVisitor { private final String methodName; private final String methodDesc; public MethodWithWholeTryCatchVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) { super(api, classVisitor); this.methodName = methodName; this.methodDesc = methodDesc; }@Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); if (mv != null & & !"< init> ".equals(name) & & methodName.equals(name) & & methodDesc.equals(descriptor)) { boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0; boolean isNativeMethod = (access & ACC_NATIVE) != 0; if (!isAbstractMethod & & !isNativeMethod) { mv = new MethodWithWholeTryCatchAdapter(api, mv, access, descriptor); } } return mv; }private static class MethodWithWholeTryCatchAdapter extends MethodVisitor { private final int methodAccess; private final String methodDesc; private final Label startLabel = new Label(); private final Label endLabel = new Label(); private final Label handlerLabel = new Label(); public MethodWithWholeTryCatchAdapter(int api, MethodVisitor methodVisitor, int methodAccess, String methodDesc) { super(api, methodVisitor); this.methodAccess = methodAccess; this.methodDesc = methodDesc; }public void visitCode() { // 首先,处理自己的代码逻辑 // (1) visitTryCatchBlock和startLabel (注意,修改的是visitTryCatchBlock方法的位置) super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, "java/lang/Exception"); super.visitLabel(startLabel); // 其次,调用父类的方法实现 super.visitCode(); }@Override public void visitMaxs(int maxStack, int maxLocals) { // 首先,处理自己的代码逻辑 // (2) endLabel super.visitLabel(endLabel); // (3) handlerLabel super.visitLabel(handlerLabel); int localIndex = getLocalIndex(); super.visitVarInsn(ASTORE, localIndex); super.visitVarInsn(ALOAD, localIndex); super.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream; "); super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "(Ljava/io/PrintStream; )V", false); super.visitVarInsn(ALOAD, localIndex); super.visitInsn(Opcodes.ATHROW); // 其次,调用父类的方法实现 super.visitMaxs(maxStack, maxLocals); }private int getLocalIndex() { Type t = Type.getType(methodDesc); Type[] argumentTypes = t.getArgumentTypes(); boolean isStaticMethod = ((methodAccess & ACC_STATIC) != 0); int localIndex = isStaticMethod ? 0 : 1; for (Type argType : argumentTypes) { localIndex += argType.getSize(); } return localIndex; } } }

转换完成之后,我们运行HelloWorldRun来查看一下输出内容:
java.lang.NullPointerException at sample.HelloWorld.test(Unknown Source) at run.HelloWorldRun.main(HelloWorldRun.java:8) Exception in thread "main" java.lang.NullPointerException at sample.HelloWorld.test(Unknown Source) at run.HelloWorldRun.main(HelloWorldRun.java:8)

我们使用javap命令查看exception table的内容:
$ javap -c sample.HelloWorld public class sample.HelloWorld { ... public void test(java.lang.String, int); Code: 0: aload_1 1: invokevirtual #20// Method java/lang/String.length:()I 4: istore_3 5: getstatic#26// Field java/lang/System.out:Ljava/io/PrintStream; 8: new#28// class java/lang/StringBuilder 11: dup 12: invokespecial #29// Method java/lang/StringBuilder."< init> ":()V 15: ldc#31// String length = 17: invokevirtual #35// Method java/lang/StringBuilder.append:(Ljava/lang/String; )Ljava/lang/StringBuilder; 20: iload_3 21: invokevirtual #38// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 24: invokevirtual #42// Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: invokevirtual #48// Method java/io/PrintStream.println:(Ljava/lang/String; )V 30: goto42 33: astore_3 34: getstatic#26// Field java/lang/System.out:Ljava/io/PrintStream; 37: ldc#50// String name is null 39: invokevirtual #48// Method java/io/PrintStream.println:(Ljava/lang/String; )V 42: aload_0 43: bipush10 45: iload_2 46: invokevirtual #54// Method div:(II)I 49: istore_3 50: getstatic#26// Field java/lang/System.out:Ljava/io/PrintStream; 53: new#28// class java/lang/StringBuilder 56: dup 57: invokespecial #29// Method java/lang/StringBuilder."< init> ":()V 60: ldc#56// String val = 62: invokevirtual #35// Method java/lang/StringBuilder.append:(Ljava/lang/String; )Ljava/lang/StringBuilder; 65: iload_3 66: invokevirtual #38// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 69: invokevirtual #42// Method java/lang/StringBuilder.toString:()Ljava/lang/String; 72: invokevirtual #48// Method java/io/PrintStream.println:(Ljava/lang/String; )V 75: return 76: astore_3 77: aload_3 78: getstatic#26// Field java/lang/System.out:Ljava/io/PrintStream; 81: invokevirtual #60// Method java/lang/Exception.printStackTrace:(Ljava/io/PrintStream; )V 84: aload_3 85: athrow Exception table: fromtotarget type 07676Class java/lang/Exception 03033Class java/lang/NullPointerException ... }

3. 操作数栈上的异常对象当方法执行过程中,遇到异常的时候,在operand stack上会有一个“异常对象”。
这个“异常对象”,可能有对应的代码处理逻辑;但是,我们想对这个“异常对象”进行多次处理,那下一步要怎么处理呢?
有两种处理方式:
  • 第一种方式,就是将“异常对象”存储到local variable内;后续使用的时候,再将“异常对象”加载到operand stack上。
  • 第二种方式,就是在operand stack上,要使用的时候,就进行一次dup(复制)操作。
3.1. 存储异常对象
对于第一种情况,我们要将“异常对象”存储到local variable当中,但是,我们应该把这个“异常对象”存储到哪个位置上呢?换句话说,我们怎么计算“异常对象”在local variable当中的位置呢?我们需要考虑三个因素:
  • 第一个因素,是否需要存储this变量。
  • 第二个因素,是否需要存储方法的参数(method parameters)。
  • 第三个因素,是否需要存储方法内部定义的局部变量(method local var)。
考虑这三个因素之后,我们就可以确定“异常对象”应该存储的一个什么位置上了。
在上面的MethodWithWholeTryCatchAdapter当中,有一个getLocalIndex()方法。这个方法就是考虑了this和方法的参数(method parameters),之后就是“异常对象”可以存储的位置。再者,方法内部定义的局部变量(method local var),有的时候有,有的时候没有,大家根据实际的情况来决定是否添加。
class MethodWithWholeTryCatchAdapter extends MethodVisitor { @Override public void visitMaxs(int maxStack, int maxLocals) { // 首先,处理自己的代码逻辑 // (2) endLabel super.visitLabel(endLabel); // (3) handlerLabel super.visitLabel(handlerLabel); int localIndex = getLocalIndex(); super.visitVarInsn(ASTORE, localIndex); super.visitVarInsn(ALOAD, localIndex); super.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream; "); super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "(Ljava/io/PrintStream; )V", false); super.visitVarInsn(ALOAD, localIndex); super.visitInsn(Opcodes.ATHROW); // 其次,调用父类的方法实现 super.visitMaxs(maxStack, maxLocals); }private int getLocalIndex() { Type t = Type.getType(methodDesc); Type[] argumentTypes = t.getArgumentTypes(); boolean isStaticMethod = ((methodAccess & ACC_STATIC) != 0); int localIndex = isStaticMethod ? 0 : 1; for (Type argType : argumentTypes) { localIndex += argType.getSize(); } return localIndex; } }

3.2. 操作数栈上复制
第二种方式,就是不依赖于local variable,也就不用去计算一个具体的位置了。那么,我们想对“异常对象”进行多次的处理,直接在operand stack进行dup(复制),就能够进行多次处理了。
import org.objectweb.asm.*; import static org.objectweb.asm.Opcodes.*; public class MethodWithWholeTryCatchVisitor extends ClassVisitor { private final String methodName; private final String methodDesc; public MethodWithWholeTryCatchVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) { super(api, classVisitor); this.methodName = methodName; this.methodDesc = methodDesc; }@Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); if (mv != null & & !"< init> ".equals(name) & & methodName.equals(name) & & methodDesc.equals(descriptor)) { boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0; boolean isNativeMethod = (access & ACC_NATIVE) != 0; if (!isAbstractMethod & & !isNativeMethod) { mv = new MethodWithWholeTryCatchAdapter(api, mv, access, descriptor); } } return mv; }private static class MethodWithWholeTryCatchAdapter extends MethodVisitor { private final int methodAccess; private final String methodDesc; private final Label startLabel = new Label(); private final Label endLabel = new Label(); private final Label handlerLabel = new Label(); public MethodWithWholeTryCatchAdapter(int api, MethodVisitor methodVisitor, int methodAccess, String methodDesc) { super(api, methodVisitor); this.methodAccess = methodAccess; this.methodDesc = methodDesc; }public void visitCode() { // 首先,处理自己的代码逻辑 // (1) startLabel super.visitLabel(startLabel); // 其次,调用父类的方法实现 super.visitCode(); }@Override public void visitMaxs(int maxStack, int maxLocals) { // 首先,处理自己的代码逻辑 // (2) endLabel super.visitLabel(endLabel); // (3) handlerLabel super.visitLabel(handlerLabel); super.visitInsn(DUP); // 注意,这里使用了DUP指令 super.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream; "); super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "(Ljava/io/PrintStream; )V", false); super.visitInsn(Opcodes.ATHROW); // (4) visitTryCatchBlock super.visitTryCatchBlock(startLabel, endLabel, handlerLabel, "java/lang/Exception"); // 其次,调用父类的方法实现 super.visitMaxs(maxStack, maxLocals); } } }


    推荐阅读