13

我目前正在尝试代理一些现有的 JAX/RS 资源,以允许我使用 Hibernate Validator 的方法验证支持。但是,当我代理我的类(当前使用 cglib 2.2)时,FormParam 注释不存在于代理类中的参数上,因此 JAX/RS 运行时(apache wink)不会填充参数。下面是一些显示这一点的测试代码:

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");
    }
}

这给了我以下输出:

类没有代理:ProxyTester$ProxyMe
 方法:aMethod有1个参数
  参数 0 有 1 个注释
   注释 @ProxyTester.TestAnnotation()
类的 javassist 代理:ProxyTester$ProxyMe
 方法:aMethod有1个参数
  参数 0 有 0 个注释
类的 cglib 代理:ProxyTester$ProxyMe
 方法:aMethod有1个参数
  参数 0 有 0 个注释
类的 jdk 代理:ProxyTester$ProxyMe
 方法:aMethod有1个参数
  参数 0 有 0 个注释

还有其他选择吗?

4

3 回答 3

1

我怀疑,注释不会动态添加到代理实例中。(可能是因为它并不简单)。但是,可以在调用(或过滤)期间从实际方法实例中获取注释。例如,

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);
    }
}
于 2012-09-21T21:26:02.267 回答
0

CGLib Enhancer technically is just extending from your Class. I do not know if this is feasible for you (number of objects) but how about exposing an interface instead of the class?

Object p2 = Enhancer.create(resource.getClass(),
    new Class[] { IResource.class },
    new MethodInterceptor() {
      public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3)
          throws Throwable {
            return arg3.invokeSuper(arg0, arg2);
           }
    });

Remove the annotations from the enhanced class and put them into the interface. Validate then against the interface. This might be a lot of boiler-plate interfaces for many such resources, but still seams a lot better than mapping everything to form backing objects or DTOs.

于 2011-08-04T18:56:08.620 回答
0

我从未使用过 cglib,但是我知道使用 Javassist,您可以使用调用处理程序来获取类的实例,然后有各种方法可以在不更改其注释的情况下定位某些内容。我最喜欢的方法之一是挂钩到不在该类内部但调用它的方法,然后当它调用该类内部的方法时,它可以更改字节码级别,或者使用稍慢但人类可读的高级别 api 进行调整。

这是我生活中的一些运行代码的片段,这些代码已经运行了 2 年以上,没有任何问题。这是使用 javassist。

ClassPool myPool = new ClassPool(ClassPool.getDefault());
                    myPool.appendClassPath("./mods/bountymod/bountymod.jar");
                    CtClass ctTHIS = myPool.get(this.getClass().getName());
                    ctCreature.addMethod(CtNewMethod.copy(ctTHIS.getDeclaredMethod("checkCoinBounty"), ctCreature, null));
                    ctCreature.getDeclaredMethod("modifyFightSkill").instrument(new ExprEditor() {
                        public void edit(MethodCall m)
                                throws CannotCompileException {
                            if (m.getClassName().equals("com.wurmonline.server.players.Player")
                                    && m.getMethodName().equals("checkCoinAward")) {
                                String debugString = "";
                                if (bDebug)
                                    debugString = "java.util.logging.Logger.getLogger(\"org.gotti.wurmunlimited.mods.bountymod.BountyMod"
                                            + "\").log(java.util.logging.Level.INFO, \"Overriding checkCoinAward to checkCoinBounty\");\n";
                                m.replace(debugString + "$_ = checkCoinBounty(player);");
                            }
                        }
                    });

它获取默认的类池。然后为我们想要从中获取方法的静态类添加完成。但是,该方法不在最终类中,而是在我们在运行时注入原始 jar 的 mod 类中。然后它在该类中获取一个方法。然后它获取与我们从 myPool 获得的单个类实例相关联的每个类的所有整个 classPool。为什么要这样做,限制我们搞砸或可能搞砸的程度,在代码生成期间占用一点内存。

然后它向 ctCreature 添加了一个新方法,这是我们之前在变量中启动的一个类,对此感到抱歉。该方法是在这个类中创建的,然后复制到另一个我们想要使用它的类中。然后它挂钩到 modifyFightSkill 的声明方法,在这种情况下,当我们想要调用我们的代码时,它就发生了。所以当它触发时,我们启动一个新的表达式编辑器。

然后,那个表达式编辑器确保我们想要在其中的实际类但不会弄乱它的任何原始结构,并在不改变其核心类的情况下获取我们想要做某事的类中的方法。

一旦发生这种情况并使用 ifs 和 goodies 进行验证,该方法就会用我们通过 replace 的新方法替换原始方法。取出旧的,放入新的。包含类中的所有注释都保持不变,因为我们从侧面偷偷地进来了。

现在我们可以直接跳入我们需要的类,点击那个方法,做我们想做的事。然而,我们最终会遇到问题,或者弄乱构造函数或其他事情。在处理大型项目时,进行代码注入的方式与任何事情一样重要。

这个答案可能有点长,但 InvokationHandler 一开始可能有点难以掌握。

于 2017-08-05T19:12:03.263 回答