Search code examples
javacglibdynamic-proxy

Invoke an interface default method on a proxy


How do I create a proxy and invoke default interface methods as if they were implemented by the proxy super-class? For example:

interface Foo {

    default int returnSomething() {
        return 1;
    }

}

interface Bar extends Foo {

    default int returnSomethingMore() {
        return returnSomething();
    }

}

class Super implements Foo {

    @Override
    public int returnSomething() {
        return 2;
    }

}

I need a proxy of Bar, which will use the Super::returnSomething implementation when calling Bar::returnSomethingMore.

I tried this:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Super.class);
enhancer.setInterfaces(new Class[] { Bar.class });
enhancer.setCallback((obj, method, args, proxy) -> {
    if (method.getName().equals("returnSomethingMore")) {

        return proxy.invokeSuper(obj, args);
        // -> NoSuchMethodError

        return proxy.invoke(obj, args);
        // -> StackOverflowError

        Class<?> declaringClass = method.getDeclaringClass();
        Lookup lookup = MethodHandles.lookup();
        return MethodHandles
                .privateLookupIn(declaringClass, lookup)
                .unreflectSpecial(method, declaringClass)
                .bindTo(obj)
                .invokeWithArguments(args);
        // -> returns 1, not 2
    }
});

How do I create a proxy object whose returnSomethingMore method returns 2?


Solution

  • I've dropped cglib (which thanks to SO's tag I've known it's no longer in active development) and adopted ByteBuddy which gave me the proxy that behaves as I need:

    @Test
    public void defaultInterfaceMethodTest() {
        Class<? extends Super> loaded = new ByteBuddy()
                .subclass(Super.class)
                .implement(Bar.class).make()
                .load(this.getClass().getClassLoader()).getLoaded();
        Bar newInstance = (Bar) loaded.getConstructor().newInstance();
        int shouldBeTwo = newInstance.returnSomethingMore();
        Assert.assertEquals(2, shouldBeTwo);
    }