Laird Nelson
Laird Nelson

Reputation: 16238

Why does ByteBuddy route method delegation to the "wrong" method in this scenario?

I am putting together a very simple ByteBuddy delegate/proxy class.

The intention is (again, very simple) to proxy a class such that any of its non-final, non-private, non-static methods and so forth get routed to equivalent methods on its proxiedInstance field as returned by its getProxiedInstance method. (There should be exceptions made for the usual suspects: equals, hashCode, wait and notify and so on.)

I've set up my proxy class using the subclass strategy. I've also defined two methods, getProxiedInstance and setProxiedInstance, along with a private field named proxiedInstance of the proper type. I've done this using the FieldAccessor.ofBeanProperty() strategy. I've omitted that here for brevity and clarity. The class does in fact contain this field and these two methods.

Then I've defined the method interception like this, statically importing the relevant ElementMatchers methods:

builder
      .method(not(isFinal()).and(not(isStatic())).and(not(isPrivate()))
              .and((isPublic().and(named("toString")).and(takesArguments(0)).and(returns(String.class)))
                   .or((not(isDeclaredBy(Object.class)).and(not(named("getProxiedInstance"))).and(not(named("setProxiedInstance"))))))
              )
      .intercept(MethodDelegation.toMethodReturnOf("getProxiedInstance"));

In English: not final, not static, not private, and either the public String toString() method inherited from Object (or overridden), or any other method not declared by Object.class and not named getProxiedInstance or setProxiedInstance.

Suppose I have a class like this:

public class Frob {

  public String sayHello() {
    return "Hello!";
  }

}

When I create a proxy class for it, instantiate it, and then call toString() on the proxy, I get Hello!.

This suggests to me somehow that the recipe I've quoted above is somehow routing toString() to sayHello().

From reading the MethodDelegation javadocs, it seems that maybe sayHello on the target/delegate object is picked for delegation because it is more specific than the method invoked on the proxy (toString). I guess name matching is lower priority than that.

I think this use case I have is relatively simple. How do I best accomplish it?

Upvotes: 1

Views: 924

Answers (2)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44067

I think using MethodCall is a better approach to this. MethodDelegation is meant for "catch all proxies" where you inject corresponding dispatchers into what is often a single delegate method, maybe two. Method call is also much more performance since it does not need to do the analysis but just reroutes to a method of a compatible type.

Upvotes: 1

Laird Nelson
Laird Nelson

Reputation: 16238

The best I could do, which works, but seems perhaps a little clunky or verbose, was this:

builder = builder
      .method(not(isDeclaredBy(Object.class))
              .and(not(isFinal()))
              .and(not(isStatic()))
              .and(not(isPrivate()))
              .and(not(named("getProxiedInstance")))
              .and(not(named("setProxiedInstance"))))
      .intercept(MethodDelegation.toMethodReturnOf("getProxiedInstance"))
      .method(is(toStringMethod))
      .intercept(invoke(toStringMethod).onMethodCall(invoke(named("getProxiedInstance"))));

(toStringMethod is the Method resulting from Object.class.getMethod("toString").)

Upvotes: 1

Related Questions