user1430281
user1430281

Reputation: 31

Implementing around-advice with a MethodDelegation

I'm having great fun with the method delegation described here:

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

This works nicely:

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

I can intercept calls and capture arguments passed to methods, which is half of what I want to achieve. However, I haven't found an equally succinct way of capturing the return value. I know I can get a Callable passed to the interceptor which performs the call, but going down that road seems like a sure way to mess up my stacktraces.

It seems to me there should be an easy and canonical-ish way to implement the "around-method" pattern.

Before I start digging into the APIs for reals: Am I missing something?

Upvotes: 3

Views: 1047

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

No, you are not missing anything.

Whenever you manipulate code with Byte Buddy, this manipulation will be reflected by the stack traces of your application. This is intentional as it makes debugging much easier in case that something goes wrong. Think of your log interceptor throwing a runtime exception; if the intercept was somehow merged into your original method, this would be quite confusing for other developers to figure out. With Byte Buddy's approach, you can simply navigate to the causing source as your interceptor is in fact available from there. With Byte Buddy, no exception is ever thrown from generated code such that any problem can be traced back to source code.

Also, merging stack frames can have strange side-effects to caller sensitive code. For example, a security manager might give higher permissions to an interceptor than to the intercepted code. Merging stack frames would revert these permissions.

Writing an interceptor with a @Super Callable injected is the canonical way for implementing arround-advice. Do not worry about performance either. Byte Buddy is written in a way that makes it very easy for the JIT compiler to inline code such that the super method call is most likely executed with zero overhead. (There is even a benchmark demonstrating that.) For your example, generic arround-adivce would look like the following:

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));
    }
  }
}

For every method, the time it takes to execute is now printed to the console. You delegate to this code using MethodDelegation.to(TimingInterceptor.class).

Make sure that you use the @RuntimeType annotation. This way, Byte Buddy attempts a casting at runtime, making this generic interception possible.

Upvotes: 2

Related Questions