Search code examples
javajava-bytecode-asmcglib

How to implement a delegation using cglib?


Here I required to create a instance of BImpl but BImpl requires to access functionality by an interface A. For this purpose, the class implements this interface A. How Can I wire a delegation of these interface methods of BImpl at runtime ? The idea is that BImpl can use A's methods.

In my case A is known and the AImpl instance is created at run-time.

public static void main(String[] args) {
  B b = (B) Enhancer.create(BImpl.class, new MyInterceptor());
  System.out.println(b.cellValue());
}

interface A {
  String value();
}

class AImpl implements A {

  @Override
  public String value() {
    return "MyA";
  }
}

interface B {
  String cellValue();
}

abstract class BImpl implements B, A {
  @Override
  public String cellValue() {
    return value() + "MyBCell";
  }
}

class MyInterceptor implements MethodInterceptor {

  @Override
  public Object intercept(Object obj, Method method, Object[] args,
                          MethodProxy proxy) throws Throwable {
    System.out.println(method.getName());
    if ("value".equals(method.getName()))
      return method.invoke(obj, args);
    else
      return proxy.invokeSuper(obj, args);
    }
  }

Solution

  • What you describe sounds like a mixin pattern. Can you not simply implement a delegation explicitly? Something like:

    class BImpl implements B, A {
    
      private A a;
    
      public BImpl(A a) {
        this.a = a;
      }
    
      @Override
      public String cellValue() {
        return value() + "MyBCell";
      }
    
      @Override
      public String value() {
        return a.value();
      }
    }
    

    This should be possible since you say that you know A during compilation.

    Otherwise, your approach is probably the correct one for cglib if you are really need to do this at runtime. However, you need to hand a different instance for obj when calling:

    return method.invoke(obj, args);
    

    Otherwise you call the intercepted method again and find yourself in an endless loop by hitting MyMethodInterceptor in a loop. You could also safe yourself the branch and use a CallbkackFilter instead.

    If you are not bound to cglib, you could also look into my library Byte Buddy which makes this all a little bit more expressive:

    new ByteBuddy()
      .subclass(BImpl.class)
      .method(isAnnotatedBy(A.class))
      .intercept(Forwarding.to("a", A.class))
      .make();
    

    The generated class has now a field a which you can set to an instance of any A to which the method invocations of the A interface methods are now delegated. You can set this field by for example using Java reflection. Otherwise, you can use a static field by

    Forwarding.to(new A() {
      // A methods
    });