2011-08-04 9 views
11

Actualmente estoy tratando de usar el proxy de algunos recursos JAX/RS existentes, para permitirme usar el soporte de validación de métodos del Validador de Hibernate. Sin embargo, cuando proxy mi clase (actualmente usando cglib 2.2), la anotación FormParam no está presente en los parámetros en la clase proxy, por lo que el tiempo de ejecución JAX/RS (apache wink) no está rellenando los parámetros. Aquí hay un código de prueba que muestra esto:¿Cómo puedo crear un proxy dinámico en Java que conserve las anotaciones de parámetros en los métodos?

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

Esto me da el siguiente resultado:

 
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 

¿Hay otras alternativas?

Respuesta

0

CGLib Enhancer se está extendiendo técnicamente desde su Clase. No sé si esto es factible para usted (número de objetos), pero ¿qué le parece exponer una interfaz en lugar de la clase?

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

Elimine las anotaciones de la clase mejorada y colóquelas en la interfaz. Validar luego contra la interfaz. Esta podría ser una gran cantidad de interfaces de placa de caldera para muchos de esos recursos, pero aún así resulta mucho mejor que mapear todo para formar objetos de respaldo o DTO.

+0

Intenté esto con proxies Java, pero no lo probé específicamente para cglib. Lo probaré y veré si cambia algo. Debo decir que no tengo esperanzas, sin embargo. –

+0

Intenté esto, fue en vano :-(. –

+0

¿Puedes probar si este validador de Spring funciona con interfaces en absoluto? Así que elimina las anotaciones de la clase, implementa la interfaz y ponla en el validador? –

1

Sospecho que las anotaciones no se agregan dinámicamente a las instancias de proxy. (probablemente porque no es sencillo). Sin embargo, es posible obtener las anotaciones de la instancia del método real durante la invocación (o el filtrado). Por ejemplo,

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

nunca he trabajado con cglib sin embargo sé que con Javassist puede utilizar las invocationhandlers para obtener una instancia de una clase, y luego hay todo tipo de maneras de dirigir algo sin cambiar sus anotaciones . Una de mis formas favoritas es conectarme a un método que no está dentro de esa clase, pero llama a él, luego cuando llama al método dentro de esa clase puede hacer cambios de nivel de código de bytes, o usar el alto más lento pero más legible para los humanos api nivel para hacer sus ajustes.

Este es un snippit de algunos de los códigos de ejecución de mi vida que funcionó durante más de 2 años sin problemas. Esto es usando 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);"); 
          } 
         } 
        }); 

Obtiene el grupo de clase predeterminado. Luego agrega el final para una clase estática de la que queremos enganchar un método. Sin embargo, ese método no está en la clase final, es en la clase de mod que estamos inyectando en el jar original en el tiempo de ejecución. Luego obtiene un método dentro de esa clase. Luego obtiene toda la clase Pool de cada clase que está asociada con la instancia de clase única que obtuvimos de myPool. ¿Por qué esto? Limita la cantidad de errores o errores que pueden ocasionar, se pierde un poco la memoria durante la generación del código.

Luego agrega un nuevo método para ctCreature que es una clase que iniciamos en una variable anterior, lo sentimos al respecto. El método se crea en esta clase, pero luego se copia en otra clase donde queremos usarlo. Luego se engancha al método declarado de modifyFightSkill, que en este caso sucede justo cuando queremos llamar a nuestro código. Entonces, cuando se dispara, comenzamos un nuevo Editor de expresiones.

Ese Expression Editor se asegura de que la clase real en la que queremos estar pero sin arruinar ninguna de sus construcciones originales, y obtiene el método dentro de la clase con la que queremos hacer algo sin cambiar su clase principal.

Una vez que eso sucede y todo se valida con ifs y goodies, el método reemplaza el método original, con nuestro nuevo método mediante replace. Saca el anterior, coloca el nuevo. Todas tus anotaciones de la clase contenedora están intactas porque porque entramos furtivamente desde un lado.

Ahora podríamos haber saltado directamente a la clase que necesitábamos, presionar ese método, hacer lo que queremos. Sin embargo, hubiésemos tenido problemas o constructores u otras cosas. La forma en que te acercas a la inyección de tu código es tan importante como cualquier otra cosa cuando trabajas con un proyecto grande.

Esta respuesta puede haber sido un poco larga, pero la invocación de invocación puede ser un poco difícil de entender al principio.

Cuestiones relacionadas