Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a dynamic proxy in java that retains parameter annotations on methods?

I currently am trying to proxy some existing JAX/RS resources, in order to allow me to use the Hibernate Validator's method validation support. However, when I proxy my class (currently using cglib 2.2), the FormParam annotation is not present on parameters in the proxy class, and so the JAX/RS runtime (apache wink) is not populating parameters. Here's some test code that shows this:

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import javassist.util.proxy.ProxyFactory;

public class ProxyTester {

    @Target( { PARAMETER })
    @Retention(RUNTIME)
    public static @interface TestAnnotation {
    }

    public static interface IProxyMe {
        void aMethod(@TestAnnotation int param);
    }

    public static class ProxyMe implements IProxyMe {
            public void aMethod(@TestAnnotation int param) {
        }
    }

    static void dumpAnnotations(String type, Object proxy, Object forObject,
            String forMethod) {
        String className = forObject.getClass().getName();

        System.err.println(type + " proxy for Class: " + className);

        for (Method method : proxy.getClass().getMethods()) {
            if (method.getName().equals(forMethod)) {
                final int paramCount = method.getParameterTypes().length;
                System.err.println(" Method: " + method.getName() + " has "
                        + paramCount + " parameters");
                int i = 0;
                for (Annotation[] paramAnnotations : method
                        .getParameterAnnotations()) {
                    System.err.println("  Param " + (i++) + " has "
                            + paramAnnotations.length + " annotations");
                    for (Annotation annotation : paramAnnotations) {
                        System.err.println("   Annotation "
                                + annotation.toString());
                    }
                }
            }
        }
    }

    static Object javassistProxy(IProxyMe in) throws Exception {
        ProxyFactory pf = new ProxyFactory();
        pf.setSuperclass(in.getClass());
        Class c = pf.createClass();
        return c.newInstance();
    }

    static Object cglibProxy(IProxyMe in) throws Exception {
        Object p2 = Enhancer.create(in.getClass(), in.getClass()
                .getInterfaces(), new MethodInterceptor() {
            public Object intercept(Object arg0, Method arg1, Object[] arg2,
                    MethodProxy arg3) throws Throwable {
                return arg3.invokeSuper(arg0, arg2);
            }
        });
        return p2;

    }

    static Object jdkProxy(final IProxyMe in) throws Exception {
        return java.lang.reflect.Proxy.newProxyInstance(in.getClass()
                .getClassLoader(), in.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        return method.invoke(in, args);
                    }
                });
    }

    public static void main(String[] args) throws Exception {
        IProxyMe proxyMe = new ProxyMe();
        dumpAnnotations("no", proxyMe, proxyMe, "aMethod");
        dumpAnnotations("javassist", javassistProxy(proxyMe), proxyMe,
            "aMethod");
        dumpAnnotations("cglib", cglibProxy(proxyMe), proxyMe, "aMethod");

        dumpAnnotations("jdk", jdkProxy(proxyMe), proxyMe, "aMethod");
    }
}

This gives me the following output:

no proxy for Class: ProxyTester$ProxyMe
 Method: aMethod has 1 parameters
  Param 0 has 1 annotations
   Annotation @ProxyTester.TestAnnotation()
javassist proxy for Class: ProxyTester$ProxyMe
 Method: aMethod has 1 parameters
  Param 0 has 0 annotations
cglib proxy for Class: ProxyTester$ProxyMe
 Method: aMethod has 1 parameters
  Param 0 has 0 annotations
jdk proxy for Class: ProxyTester$ProxyMe
 Method: aMethod has 1 parameters
  Param 0 has 0 annotations

Are there any other alternatives?

like image 758
Peter Hart Avatar asked Aug 04 '11 17:08

Peter Hart


People also ask

What is Java dynamic proxy?

A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface.

Which annotation is used in Spring to create proxy instance for the interface?

When a Spring JDK proxy is used, the join point annotation should be present on both the interface's method and the concrete class's method for the aspect to trigger correctly. The proves that both the interface and the concrete class require the join point annotation when a JDK proxy is used.

Is JDK a dynamic proxy?

JDK dynamic proxy is available with the JDK. It can be only proxy by interface so target class needs to implement interface. In your is implementing one or more interface then spring will automatically use JDK dynamic proxies. On the other hand, CGLIB is a third party library which spring used for creating proxy.

How are JDK dynamic proxy and Cglib proxies used in Spring?

From Spring documentation : Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice). If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used.


1 Answers

I suspect, annotations are not added dynamically to the proxy instances. (probably because it is not straightforward). However it is possible to get the annotations from actual method instance during invocation (or filtering). For example,

import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyTester
{
    @Target({ PARAMETER })
    @Retention(RUNTIME)
    public static @interface TestAnnotation {}

    public static interface IProxyMe {
        void aMethod(@TestAnnotation int param);
    }

    public static class ProxyMe implements IProxyMe {
        public void aMethod(@TestAnnotation int param) {
            System.out.println("Invoked " + param);
            System.out.println("-----------------");
        }
    }

    static void dumpAnnotations(String type, Object proxy, Object forObject, String forMethod)
    {
        String className = forObject.getClass().getName();
        System.out.println(type + " proxy for Class: " + className);

        for(Method method : proxy.getClass().getMethods()) {
            if(method.getName().equals(forMethod)) {
                printAnnotations(method);
            }
        }
    }

    static void printAnnotations(Method method)
    {
        int paramCount = method.getParameterTypes().length;
        System.out.println("Method: " + method.getName() + " has "  + paramCount + " parameters");

        for(Annotation[] paramAnnotations : method.getParameterAnnotations())
        {
            System.out.println("Annotations: " + paramAnnotations.length);

            for(Annotation annotation : paramAnnotations)
            {
                System.out.println("   Annotation " + annotation.toString());
            }
        }
    }

    static Object javassistProxy(IProxyMe in) throws Exception
    {
        ProxyFactory pf = new ProxyFactory();
        pf.setSuperclass(in.getClass());

        MethodHandler handler = new MethodHandler()
            {
                public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable
                {
                    if(thisMethod.getName().endsWith("aMethod"))
                        printAnnotations(thisMethod);

                    return proceed.invoke(self, args);
                }
            };
        return pf.create(new Class<?>[0], new Object[0], handler);
    }

    static Object cglibProxy(IProxyMe in) throws Exception
    {
        Object p2 = Enhancer.create(in.getClass(), in.getClass().getInterfaces(),
            new MethodInterceptor()
            {
                public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
                {
                    printAnnotations(arg1);
                    return arg3.invokeSuper(arg0, arg2);
                }
            });

        return p2;
    }

    static Object jdkProxy(final IProxyMe in) throws Exception
    {
        return java.lang.reflect.Proxy.newProxyInstance(in.getClass().getClassLoader(), in.getClass().getInterfaces(),
            new InvocationHandler()
            {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
                {
                    printAnnotations(method);
                    return method.invoke(in, args);
                }
            });
    }

    public static void main(String[] args) throws Exception
    {
        IProxyMe proxyMe = new ProxyMe();
        IProxyMe x = (IProxyMe) javassistProxy(proxyMe);
        IProxyMe y = (IProxyMe) cglibProxy(proxyMe);
        IProxyMe z = (IProxyMe) jdkProxy(proxyMe);

        dumpAnnotations("no", proxyMe, IProxyMe.class, "aMethod");
        dumpAnnotations("javassist", x, IProxyMe.class, "aMethod");
        dumpAnnotations("cglib", y, IProxyMe.class, "aMethod");
        dumpAnnotations("jdk", z, IProxyMe.class, "aMethod");

        System.out.println("<<<<< ---- Invoking methods ----- >>>>>");
        x.aMethod(1);
        y.aMethod(2);
        z.aMethod(3);
    }
}
like image 108
Mohan Avatar answered Oct 23 '22 09:10

Mohan