Search code examples
javabyte-buddy

How can I bind constructor parameter to a instance field?


Is it possible create new final field on a class and create a constructor with parameter that is set to that final field on class instantiation? I tried several approaches and searched for answers but found no example how to do it.

What I have now:

Class proxyClass = new ByteBuddy().subclass(Object.class).implement((Type[]) interfaces)
                        .defineField("dispatcherInvocationHandler", ByteBuddyDispatcherInvocationHandler.class, Modifier.PRIVATE + Modifier.FINAL)
                        .defineConstructor(Modifier.PUBLIC)
                        .withParameter(ByteBuddyDispatcherInvocationHandler.class)
                        .intercept(MethodCall.invokeSuper().andThen(/* copy argument to field */))
                        .method(ElementMatchers.any())
                        .intercept(InvocationHandlerAdapter.toField("dispatcherInvocationHandler"))
                        .make()
                        .load(BytebuddyProxyGenerator.class.getClassLoader())
                        .getLoaded();

I don't want to make field public and I don't want to create property with setter / getter to that field. I'd like to end up with something like:

public class DontCare {
    private final ByteBuddyDispatcherInvocationHandler dispatcherInvocationHandler;

    public DontCare(ByteBuddyDispatcherInvocationHandler arg) {
       super();
       this.dispatcherInvocationHandler = arg;
    }
}

Is there any simple way how to do this?

Updated 31.10.2016

Finally I got it working with the help of Rafael and 1.5.2 version of the ByteBuddy library. Working code is this:

Class proxyClass = new ByteBuddy().subclass(Object.class).implement((Type[]) interfaces)
                        .defineField("dispatcherInvocationHandler", ByteBuddyDispatcherInvocationHandler.class, Modifier.PRIVATE + Modifier.FINAL)
                        .defineConstructor(Modifier.PUBLIC)
                        .withParameter(ByteBuddyDispatcherInvocationHandler.class)
                        .intercept(
                                MethodCall.invoke(Object.class.getConstructor())
                                        .onSuper()
                                        .andThen(
                                                FieldAccessor.ofField("dispatcherInvocationHandler")
                                                        .setsArgumentAt(0)
                                        )
                        )
                        .method(ElementMatchers.any())
                        .intercept(InvocationHandlerAdapter.toField("dispatcherInvocationHandler"))
                        .make()
                        .load(ByteBuddyProxyGenerator.class.getClassLoader())
                        .getLoaded();

Solution

  • Unfortunately, there is no good way in the moment. I did however take this as an inspiration to refactor the FieldAccessor implementation which now allows you to do the following:

    builder.defineField("desiredField", 
              DispatcherInvocationHandler.class, 
              Visibility.PRIVATE, FieldManifestation.FINAL)
           .defineConstructor(Visibility.PUBLIC)
           .withParameters(String.class)
           .intercept(MethodCall.invokeSuper() // Given such a constructor exists
             .andThen(FieldAccessor.ofField("desiredField").setsArgumentAt(0)))
           .make()
           .load(BytebuddyProxyGenerator.class.getClassLoader())
           .getLoaded();
    

    When you define such an explicit setter, the instrumentation also becomes chainable by itself. You can therefore set multiple fields such as:

    FieldAccessor.ofField("foo")
                 .setsArgumentAt(0)
                 .andThen(FieldAccessor.ofField("bar").setsArgumentAt(1));
    

    With the above implementation, a constructor (or any method) would be implemented similar to:

    class Sample {
      Object foo, bar;
      Sample(Object a1, Object a2) {
        foo = a1;
        bar = a2;
      }
    }
    

    This additional API is released with Byte Buddy 1.5.2.