4

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();
Cœur
  • 37,241
  • 25
  • 195
  • 267
Novoj
  • 329
  • 2
  • 6

1 Answers1

3

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.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Regretfully I'm getting exception: `java.lang.IllegalStateException: public net.bytebuddy.renamed.java.lang.Object$ByteBuddy$8eiOi2jB(com.fg.generation.specific.bytebuddy.ByteBuddyDispatcherInvocationHandler) does not take 0 arguments` I tried to debug it, but there is no possible way how to add .withAllArguments close to the FieldAccessor code. Am I doing something wrong? – Novoj Oct 31 '16 at 19:54
  • I've updated example in the former question - it reflects the code I have now. – Novoj Oct 31 '16 at 20:02
  • I assume that comes from the super method call. What is the super class and what constructors does it take? If you supclass object, change MethodCall to invoke the Object default constructor. – Rafael Winterhalter Oct 31 '16 at 21:08
  • Thank you very much, it works! Though I don't much recognize the difference between MethodCall.invokeSuper() and Method.invoke(constructor).onSuper(). I would naively think that invokeSuper() variant will call default constructor on super object in this case. But thinking about it more deeper, there is no overloaded variant of invokeSuper() to pass arguments, so it wouldn't be possible to handle other possible cases (for example invoking other parametrized constructor). – Novoj Oct 31 '16 at 21:46
  • Invoke super invokes the direct super method, if it exists, with the same arguments as the method instrumented. I will try to improve the documentation. Glad it works! – Rafael Winterhalter Oct 31 '16 at 21:50