Search code examples
javabytecodeinstrumentationbyte-buddy

Set an instanceField in Byte Buddy


I'm trying to figure out how to set the value of an instance field using Byte Buddy. The documentation says:

Always remember to assign a value to this field before calling methods on an instance of such a dynamic class. Otherwise, a method delegation will result in a NullPointerException.

But I don't see anywhere in the documentation or the unit tests on how to do this.

My dynamic class is:

new ByteBuddy().subclass(AbstractService.class)
        .name(serviceName)
        .method(ElementMatchers.named("start").and(
                ElementMatchers.takesArguments(0)))
        .intercept(
                MethodDelegation.toInstanceField(service, "consumer")
                    .filter(ElementMatchers.isAnnotatedWith(Start.class)))
        .method(ElementMatchers.named("stop").and(
                ElementMatchers.takesArguments(0)))
        .intercept(
                MethodDelegation.to(instance).filter(
                        ElementMatchers.isAnnotatedWith(Stop.class)))
        .make();

I see another post with an answer to intercept any constructor and use @FieldProxy with a MethodDelegation but I don't see how to do it. Everything I've tried in terms of results in some variation of .constructor(ElementMatchers.any()).intercept(...) results in:

java.lang.IllegalArgumentException: None of [] allows for delegation from...


Solution

  • Basically, when you are using MethodDelegation.toInstanceField, Byte Buddy adds a field of the given name to the generated class. In your case, Byte Buddy added a field of type service named "consumer".

    You now need to decide how you want to assign a value to this field as the field does not have a value, i.e. is null prior to assignment. If you called a method that is annotated with @Start before doing so, you would encounter a NullPointerException.

    The easiest way to assign the field would be reflection. A type-safe alternative would be to implement some interface

    interface SetterIFace {
      void setConsumer(MyType myType);
    }
    

    given that your service type is a Class<MyType>. You can then add:

    .implement(SetterIFace.class).intercept(FieldAccessor.ofBeanProperty())
    

    to implement SetterIFace as a setter of the field in question after you defined it in the MethodDelegation.