Xobotun
Xobotun

Reputation: 1371

How to get method annotations on a proxied class?

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)?

Upvotes: 4

Views: 3270

Answers (1)

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 279990

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).

Upvotes: 3

Related Questions