在try-finally块中嵌入方法的现有代码(2)

前段时间,我在try-finally块中嵌入了一个方法的现有代码,如何使用ASM将方法的主体包装在try-finally块中。 解决方案是在visitCode()visitCode()方法体开头的try块的标签,并在visitCode()访问带有返回操作码的指令时完成try-finally块。 我知道如果方法没有返回指令,如果方法总是离开并且exception,则解决方案将不起作用。

虽然,我发现前一种解决方案有时也不适用于带有返回指令的方法。 如果方法有多个返回指令,它将无法工作。 原因是它生成了无效的字节码,因为在方法的开头添加了一个try-finally块,但是完成了多个try-finally块。

通常(但可能取决于javac编译器),字节码方法包含单个返回指令,并且所有返回路径通过跳转在该指令处结束。 但是,使用Eclipse编译以下代码将导致字节代码带有两个返回指令:

 public boolean isEven(int x) { return x % 2 == 0; } 

用Eclipse编译的字节代码:

  0: iload_1 1: iconst_2 2: irem 3: ifne 8 6: iconst_1 7: ireturn // javac compilation: goto 9 8: iconst_0 9: ireturn 

因此,我想知道包装方法代码的整个代码的正确方法是什么。

你必须回顾一下Java编译器在编译try … finally …时所做的事情try … finally …这意味着将你的finally动作复制到保留(源)代码块的每个点(即返回指令)并安装多个受保护(生成的字节代码)区域(因为它们不应该覆盖你的finally动作)但是它们都可能指向同一个exception处理程序。 或者,您可以转换代码,将分支的所有返回指令替换为“after”操作的一个实例,然后单独返回指令。

这不是微不足道的。 因此,如果您不需要通常不支持向已加载类添加方法的热代码替换,则避免所有这些的最简单方法是将原始方法重命名为不与其他方冲突的名称(您可能使用不允许的字符)在普通的源代码中)并使用旧名称和签名创建一个新方法,该方法由一个简单的try … finally …构造组成,其中包含对重命名方法的调用。

例如,将public void desired()更改为private void desired$instrumented()并添加一个新的

 public void desired() { //some logging X try { desired$instrumented(); } finally { //some logging Y } } 

请注意,由于调试信息保留在重命名的方法中,因此如果在重命名的方法中引发exception,则堆栈跟踪将继续报告正确的行号。 如果你通过添加一个不可见的字符来重命名它(请记住,你在字节代码级别有更多的自由),它会非常流畅。

感谢Holger的回答和锑的评论我开发了以下解决方案,满足了我的需求。 后来我发现在使用ASM框架实现常见字节码转换模式时也描述了类似的方法,E。Kuleshov,AOSD.07,2007年3月,加拿大温哥华。

此解决方案不适用于不包含非exception返回的方法(在每个执行路径中抛出exception的方法,例如throw new NotSupportedOperationException(); )!

如果您还需要支持这些方法,则应遵循Holger的建议重命名原始方法,然后使用旧名称添加新方法。 在添加的方法中将委托调用添加到重命名的方法,并将调用嵌入到try-finally块中。


我使用一个简单的MethodVisitor来访问代码。 在visitCode()方法中,我添加了在输入方法时要执行的指令。 然后,我通过访问一个新的Label添加try块的开头。 当我访问visitInsn()的返回操作码时,我将完成try块并添加finally块。 转移,我添加一个新的Label来开始一个新的try块,以防该方法包含进一步的返回指令。 (如果没有返回说明,则标签访问不会产生任何影响。)

简化代码如下:

 public abstract class AbstractTryFinallyMethodVisitor extends MethodVisitor { private Label m_currentBeginLabel; private boolean m_isInOriginalCode = true; protected void execBeforeMethodCode() { // Code at the beginning of the method and not in a try block } protected void execVisitTryBlockBegin() { // Code at the beginning of each try block } protected void execVisitFinallyBlock() { // Code in each finally block } @Override public void visitCode() { try { m_isInOriginalCode = false; execBeforeMethodCode(); beginTryFinallyBlock(); } finally { m_isInOriginalCode = true; } } protected void beginTryFinallyBlock() { m_currentBeginLabel = new Label(); visitLabel(m_currentBeginLabel); execVisitTryBlockBegin(); } @Override public void visitInsn(int opcode) { if (m_inOriginalCode && isReturnOpcode(opcode) { try { m_isInOriginalCode = false; completeTryFinallyBlock(); super.visitInsn(opcode); beginTryBlock(); } finally { m_isInOriginalCode = true; } } else { super.visitInsn(opcode); } } protected void completeTryFinallyBlock() { Label l1 = new Label(); visitTryCatchBlock(m_currentBeginLabel, l1, l1, null); Label l2 = new Label(); visitJumpInsn(GOTO, l2); visitLabel(l1); // visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" }); visitVarInsn(ASTORE, 1); execVisitFinallyBlock(); visitVarInsn(ALOAD, 1); super.visitInsn(ATHROW); visitLabel(l2); // visitFrame(Opcodes.F_SAME, 0, null, 0, null); execVisitFinallyBlock(); } protected static boolean isReturnOpcode(int opcode) { return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN; } } 

笔记:

  • 如果使用COMPUTE_FRAMES标志实例化ClassWriter ,则COMPUTE_FRAMES调用visitFrame
  • 也可能(并且可能更好)使用AdviceAdapter并在其onMethodEnter()onMethodExit()方法中执行字节码操作。
  • 如前所述,只有在字节码包含至少一条返回指令时才会添加try-finally块。

问题的isEven()方法的转换字节代码输出将是:

 public boolean isEven(int); Code: 0: ldc #22 //isEven(int) 2: invokestatic #28 //log/Logger.push:(Ljava/lang/String;)V 5: iload_1 *1* 6: iconst_2 *1* 7: irem *1* 8: ifne 25 *1* 11: iconst_1 *1* 12: goto 21 *1* 15: astore_1 16: invokestatic #31 //log/Logger.pop:()V 19: aload_1 20: athrow 21: invokestatic #31 //log/Logger.pop:()V 24: ireturn 25: iconst_0 *2* 26: goto 35 *2* 29: astore_1 30: invokestatic #31 //log/Logger.pop:()V 33: aload_1 34: athrow 35: invokestatic #31 //log/Logger.pop:()V 38: ireturn Exception table: from to target type 5 15 15 any *1* 25 29 29 any *2* }