使用MethodDelegation实现around-advice

时间:2015-11-16 20:17:44

标签: java bytecode byte-buddy

我对这里描述的方法委派非常有趣:

http://www.javacodegeeks.com/2015/01/make-agents-not-frameworks.html

这很好用:

.intercept(MethodDelegation.to(LogInterceptor.class)
                              .andThen(SuperMethodCall.INSTANCE)

我可以拦截调用并捕获传递给方法的参数,这是我想要实现的一半。但是,我还没有找到一种同样简洁的方法来捕获返回值。我知道我可以将一个Callable传递给执行调用的拦截器,但沿着这条路走下去似乎是一个弄乱我的堆栈跟踪的可靠方法。

在我看来,应该有一种简单而规范的方式来实现“around-method”模式。

在我开始研究用于实际的API之前:我错过了什么吗?

1 个答案:

答案 0 :(得分:2)

不,你没有遗漏任何东西。

每当您使用Byte Buddy操作代码时,此操作将由应用程序的堆栈跟踪反映出来。这是故意的,因为它可以在出现问题时更轻松地进行调试。想想您的日志拦截器抛出运行时异常;如果拦截以某种方式合并到您的原始方法中,这对于其他开发人员来说是非常困惑的。使用Byte Buddy的方法,您可以直接导航到导致源,因为您的拦截器实际上可以从那里获得。使用Byte Buddy,生成的代码不会抛出异常,因此任何问题都可以追溯到源代码。

此外,合并堆栈帧可能会对调用者敏感代码产生奇怪的副作用。例如,安全管理器可能会为拦截器提供比截取的代码更高的权限。合并堆栈帧将恢复这些权限。

编写一个注入了@Super Callable的拦截器是实现arround-advice的规范方法。不要担心性能。 Byte Buddy的编写方式使JIT编译器很容易内联代码,以便超级方法调用最有可能以零开销执行。 (甚至有a benchmark demonstrating that。)对于您的示例,通用arround-adivce将如下所示:

public class TimingInterceptor {

  @RuntimeType
  public static Object intercept(@Super Callable<?> zuper) 
      throws Exception {
    long before = System.currentTimeMillis();
    try {
      return zuper.call();
    } finally {
      System.out.println("Took: " + (Systen.currentTimeMillis() - before));
    }
  }
}

对于每种方法,现在执行的时间都会打印到控制台。您可以使用MethodDelegation.to(TimingInterceptor.class)委托此代码。

确保使用@RuntimeType注释。这样,Byte Buddy在运行时尝试进行转换,使这种通用拦截成为可能。