2

I have a problem while reading annotations off methods of a proxied class.

There is an interface, an object and an annotation on a method, this part is really simple:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface A {
}

interface I {
    void method();
}

class Test implements I {
    @A
    public void method() { }
}

Next, there is an InvocationHandler that does nothing, just simply calls the method with the arguments passed:

class DefaultInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(final Object o, final Method method, final Object[] args) throws Throwable {
        return method.invoke(o, args);
    }
}

And there is a main method that prints declared methods of a Test instance and its proxied counterpart:

class Main {
    public static void main(String[] args) {
        Object test = new Test();
        printMethods(test);         // Outputs that `I#method` has `A` annotation

        System.out.println();

        Object proxied = Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(), new DefaultInvocationHandler());
        printMethods(proxied);      // Outputs that `I#method` does not have `A` annotation
    }

    static void printMethods(Object obj) {
        Arrays.stream(obj.getClass().getDeclaredMethods())
                .forEach(method -> System.out.println(method.toString() + " has A annotation: " + method.isAnnotationPresent(A.class)));
    }
}

And here comes the problem: local variable test has is an instance of Test class, and local variable proxied is actually a Proxy, so it does not have any annotations on its methods. Here's the output of the program:

public void Test.method() has A annotation: true                // <- good thing

public final boolean $Proxy2.equals(java.lang.Object) has A annotation: false
public final java.lang.String $Proxy2.toString() has A annotation: false
public final void $Proxy2.method() has A annotation: false      // <-  bad thing
public final int $Proxy2.hashCode() has A annotation: false

I've tried searching for the solution, but this question is about extracting annotations off an annotation (I presume), this one is too about annotation class. Some of them are about other proxy implementations.

➥ So, is there any way to get actual annotations off a proxied object, or to expose the class that is hidden under the proxy (I want the former one, though)?

Xobotun
  • 1,121
  • 1
  • 18
  • 29
  • 1
    A note if you missed it, in `method.invoke(o, args)`, the `o` is the `Proxy` instance. That invocation is going to go into an infinite recursion loop. – Sotirios Delimanolis Nov 07 '19 at 14:35
  • Why should the proxy have the `A` annotation? The proxy is an implementation of `I`, without any relationship to the `Test` class, so why should the annotation from an unrelated class spill over to the proxy? – Holger Nov 07 '19 at 14:54
  • @Holger, yes, now I get it, thanks. Several hours earlier I thought it `Proxy` is a wrapper to the `Test` instance, but now I see that it is the `InvocationHandler` which may hold reference to the proxied object. I should have read the documentation prior to jumping straight to examples. :D – Xobotun Nov 07 '19 at 19:53

1 Answers1

2

So, is there any way to get actual annotations off a proxied object, or to expose the class that is hidden under the proxy (I want the former one, though)?

Not directly, no.

The idea behind Proxy's design is that the actual wrapped instance (if there even is one) would be hidden behind the invocation handler. You can see this from the newProxyInstance method: there's no reference to the test instance passed anywhere. The Proxy instance has no knowledge of your Test instance.

A common pattern is to use a common InvocationHandler subclass that keeps a reference to a wrapped instance and can return it to you and you can use that to perform your checks. For example,

abstract class InvocationHandlerWithTarget implements InvocationHandler {
    protected final Object target;

    public InvocationHandlerWithTarget(Object target) {
        this.target = target;
    }

    public Object getTarget() {
        return target;
    }
}

class DefaultInvocationHandler extends  InvocationHandlerWithTarget {
    public DefaultInvocationHandler(Object target) {
        super(target);
    }

    @Override
    public Object invoke(final Object o, final Method method, final Object[] args) throws Throwable {
        return method.invoke(target, args);
    }
}

and then check if you're working with a Proxy and whether its InvocationHandler is what you expect

Object proxied = Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(),
            new DefaultInvocationHandler(test));

[...]

if (Proxy.isProxyClass(proxied.getClass())) {
    var handler = Proxy.getInvocationHandler(proxied);
    if (handler instanceof InvocationHandlerWithTarget) {
        var handlerWithTarget = (InvocationHandlerWithTarget) handler;

        // now process the target
        handlerWithTarget.getTarget();
    }
}

You then have a concrete instance with which to reflect on (or whatever other processing you need to do).

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • Thanks for the brilliant answer! It turned out I was using proxies the wrong way. Now that you have pointed out there is no reference to the `test` within the proxy instance, it looks obvious, but when I was reading examples and writing this piece of code I was completely oblivious to this fact. :/ Thanks again for the explanation! :D – Xobotun Nov 07 '19 at 19:47