前段时间在Embed the existing code of a method in a try-finally block问过如何使用 ASM 将方法主体包装在 try-finally block 中。
解决方案是在 visitCode()
中的方法体开头访问 try block 的标签,并在 visitInsn 中访问带有返回操作码的指令时完成 try-finally block ()
。我知道,如果一个方法没有返回指令,那么解决方案将无法工作,如果该方法总是以异常离开则适用。
不过,我发现前一种解决方案有时也不适合带有返回指令的方法。如果一个方法有多个返回指令,它将不起作用。原因是它生成了无效的字节码,因为在方法的开头添加了一个try-finally block ,但完成了多个try-finally block 。
通常(但可能取决于 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 …
时所做的事情,这意味着将你的 finally
操作复制到 protected (源)代码块将要执行的每个点离开(即返回指令)并安装多个 protected (生成的字节码)区域(因为它们不应该覆盖您的 finally
操作)但它们可能都指向同一个异常处理程序。或者,您可以转换代码,将所有返回指令替换为分支到您的“之后”操作的一个实例,然后是唯一的返回指令。
这不是微不足道的。因此,如果您不需要通常不支持向加载的类添加方法的热代码替换,避免这一切的最简单方法是将原始方法重命名为不与其他方法冲突的名称(您可以使用不允许的字符在普通源代码中)并使用旧名称和签名创建一个新方法,该方法由一个简单的 try … finally …
结构组成,其中包含对重命名方法的调用。
例如将 public void desired()
更改为 private void desired$instrumented()
并添加一个新的
public void desired() {
//some logging X
try {
desired$instrumented();
}
finally {
//some logging Y
}
}
请注意,由于调试信息保留在重命名的方法中,如果在重命名的方法中抛出异常,堆栈跟踪将继续报告正确的行号。如果您通过仅添加一个不可见字符来重命名它(请记住,您在字节码级别有更多的自由),它会非常顺利。
关于java - 在 try-finally block 中嵌入方法的现有代码 (2),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28415896/