How to use ByteBuddy @Pipe annotation with @FieldValue to implement delegate pattern?

1.1k views Asked by At

With ByteBuddy I'm trying to find an efficient way to generate a proxy that simply forward all method calls to an underlying delegate instance of the same type and I came accross this: How to implement a wrapper decorator in Java?, I tried to implement the suggested solution but without any success, on the surface my rough guess without knowing much about the internals of ByteBuddy is that it looks like the @FieldValue annotated parameter of the intercept method below might be taken into consideration when checking for matching delegate's method signatures? My use case is a bit more complex, but I wrote a simple unit test that reproduce the same issue, I'm using ByteBuddy version 1.5.13:

@Test
public void testDelegate() throws Exception {

    Object delegate = "aaa";

    Class<?> delegateClass = new ByteBuddy().subclass(Object.class)
            .method(ElementMatchers.any()).intercept(MethodDelegation.to(Interceptor.class).defineParameterBinder(Pipe.Binder.install(Function.class)))
            .defineField("delegate", Object.class, Modifier.PUBLIC)
            .make()
            .load(getClass().getClassLoader())
            .getLoaded();

    Object obj = delegateClass.newInstance();
    delegateClass.getField("delegate").set(obj, delegate);

    assertThat(obj, equalTo("aaa"));
}

This interceptor is working fine and the unit test pass successfully:

public static class Interceptor {

    @RuntimeType
    public static Object intercept(@Pipe Function<Object, Object> pipe) {
        return pipe.apply("aaa");
    }
}

But if I replace the above Interceptor with this one and try to inject the delegate field with @FieldValue:

public static class Interceptor {

    @RuntimeType
    public static Object intercept(@Pipe Function<Object, Object> pipe, @FieldValue("delegate") Object delegate) {
        return pipe.apply(delegate);
    }
}

I get the following error:

java.lang.IllegalArgumentException: None of [public static java.lang.Object io.github.pellse.decorator.DecoratorTest$Interceptor.intercept(java.util.function.Function,java.lang.Object)] allows for delegation from public boolean java.lang.Object.equals(java.lang.Object)
at net.bytebuddy.implementation.bind.MethodDelegationBinder$Processor.process(MethodDelegationBinder.java:881)
at net.bytebuddy.implementation.MethodDelegation$Appender.apply(MethodDelegation.java:1278)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:678)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:667)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod.apply(TypeWriter.java:586)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation.create(TypeWriter.java:4305)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1796)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:172)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:153)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:2568)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:2670)
at io.github.pellse.decorator.DecoratorTest.testDelegate(DecoratorTest.java:476)

So I'm wondering if I'm correctly using the @Pipe/@FieldValue annotations or if there is a another way to delegate method calls when generating proxies with ByteBuddy? Thanks in advance!

2

There are 2 answers

3
Rafael Winterhalter On BEST ANSWER

For forwarding, I would actually recommend you to use MethodCall.invokeSelf().onField(delegate) which is performing much better than the MethodDelegation which is applying a much more complex matching. Note that a delegation can be combined with a delegation such as MethodDelegation.to( ... ).andThen(MethodCall.invokeSelf().onField(delegate).

Also, I just changed the delegation API in the upcoming v1.6 to avoid the confusion you experienced and to allow for several performance improvements.

Important: It is not possible to pipe protected methods if those methods are defined by a type in another package than the instrumented type. Current versions of Byte Buddy miss this check but it will throw an exception from 1.6.1 on. You can exclude such methods by not(isProtected()). Object::clone is a typical candidate for such a method as it is defined in the java. package.

0
Sebastien Pelletier On

Using appendParameterBinder instead of defineParameterBinder fixed the issue, the default binders as specified by TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS in MethodDelegation.to(TypeDescription typeDescription) were overridden so the @FieldValue binder was not defined anymore, my mistake. Everyday I'm using ByteBuddy I'm more and more impressed by the quality of that library!