如何在java中创建一个保留方法参数注释的动态代理?

我目前正在尝试代理一些现有的JAX / RS资源,以便允许我使用Hibernate Validator的方法validation支持。 但是,当我代理我的类(当前使用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"); } } 

这给了我以下输出:

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

还有其他选择吗?

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

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

CGLib Enhancer技术上只是从您的类扩展。 我不知道这对你来说是否可行(对象数量)但是如何暴露界面而不是类呢?

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

从增强类中删除注释并将其放入界面中。 然后根据接口进行validation。 对于许多此类资源而言,这可能是许多样板接口,但仍然比将所有内容映射到形成后备对象或DTO更好。

我从未使用过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);"); } } }); 

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

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

表达式编辑器然后确保我们想要的实际类,但不要弄乱它的任何原始构造,并获取该类中的方法,我们希望在不更改其核心类的情况下执行某些操作。

一旦发生这种情况并且使用ifs和goodies进行了全部validation,该方法将替换原始方法,并使用我们的新方法替换。 取出旧的,将新的注释放入。所有来自包含类的注释都不受影响因为因为我们从侧面偷偷溜进来。

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

这个答案可能有点长,但InvokationHandler起初可能有点难以掌握。