2011-08-04 10 views
11

Obecnie próbuję wprowadzić proxy niektórych istniejących zasobów JAX/RS, aby umożliwić mi korzystanie z funkcji sprawdzania poprawności metody sprawdzania poprawności przez Hibernate Validator. Jednak podczas pośredniczenia w mojej klasie (obecnie przy użyciu cglib 2.2) adnotacja FormParam nie występuje w parametrach klasy proxy, a zatem środowisko wykonawcze JAX/RS (mrugnięcie apache) nie wypełnia parametrów. Oto niektóre kodu testu, który pokazuje to:Jak mogę utworzyć dynamiczny proxy w java, który zachowuje adnotacje parametrów metod?

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

To daje mi następujący wynik:

 
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 

są jakieś inne alternatywy?

Odpowiedz

0

CGLib Enhancer technicznie właśnie rozszerza swoją klasę. Nie wiem, czy jest to wykonalne dla ciebie (liczba obiektów), ale co z ekspozycją interfejsu zamiast klasy?

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

Usuń adnotacje z rozszerzonej klasy i umieść je w interfejsie. Sprawdź poprawność wobec interfejsu. Może to być wiele interfejsów kotła-płyty dla wielu takich zasobów, ale wciąż szwy są o wiele lepsze niż mapowanie wszystkiego w celu utworzenia obiektów zaplecza lub DTO.

+0

Próbowałem tego z serwer proxy java, ale nie próbowałem specjalnie dla cglib. Wypróbuję to i zobaczę, czy to coś zmieni. Muszę powiedzieć, że nie mam jednak nadziei. –

+0

Próbowałem tego, na próżno :-(. –

+0

Czy możesz spróbować, jeśli ten wiosenny weryfikator działa w ogóle z interfejsami? Więc usuń adnotacje z klasy, zaimplementuj interfejs i umieść go w walidatorze? –

1

Podejrzewam, adnotacje nie są dodawane dynamicznie do instancji proxy. (prawdopodobnie dlatego, że nie jest to proste). Możliwe jest jednak uzyskanie adnotacji z rzeczywistej instancji metody podczas wywoływania (lub filtrowania). Na przykład,

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

Nigdy nie pracowałem z cglib jednak wiem, że z Javassist można użyć invocationhandlers uzyskać instancji klasy, a następnie są wszystkie rodzaje sposobów kierowanych coś bez zmiany jego adnotacje . Jednym z moich ulubionych sposobów jest podpięcie się do metody, która nie znajduje się wewnątrz tej klasy, ale wywołuje ją, a następnie, gdy wywołuje metodę wewnątrz tej klasy, może dokonać zmian w poziomie bajtowym, lub użyć nieco wolniejszej, ale czytelnej dla człowieka wysokiej wartości. level api, aby dokonać korekty.

To jest fragment kodu z mojego działającego kodu, który działał przez ponad 2 lata bez żadnych problemów. To używa 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);"); 
          } 
         } 
        }); 

Otrzymuje domyślną pulę klas. Następnie dodaje wykończenie klasy statycznej, od której chcemy przechwycić metodę. Jednak ta metoda nie znajduje się w końcowej klasie, jest w klasie mod, którą wstrzykiwamy do oryginalnego słoika w czasie wykonywania. Następnie dostaje metodę wewnątrz tej klasy. Następnie pobiera całą klasę klasy dla każdej klasy, która jest powiązana z pojedynczą instancją klasy, którą otrzymaliśmy z myPool. Dlaczego tak się dzieje, ogranicza to, jak bardzo się z tym komunikujemy, lub co może zepsuć, trochę traci pamięci podczas generowania kodu.

Następnie dodaje nową metodę do ctCreature, która jest klasą, którą zainicjowaliśmy w zmiennej wcześniej przepraszam za to. Metoda jest tworzona w tej klasie, ale następnie kopiowana do innej klasy, z której chcemy go używać. Następnie dołącza się do deklarowanej metody modifyFightSkill, która w tym przypadku dzieje się dokładnie wtedy, gdy chcemy wywołać nasz kod. Kiedy uruchamiamy, uruchamiamy nowy edytor wyrażeń.

Ten edytor wyrażeń zapewnia, że ​​rzeczywista klasa, którą chcemy być wewnątrz, ale nie zepsuć jej oryginalnej konstrukcji, dostaje metodę wewnątrz tej klasy, z którą chcemy coś zrobić bez zmiany jej podstawowej klasy.

Kiedy to się stanie i wszystko zostanie sprawdzone przy pomocy ifs i gadżetów, metoda zastąpi oryginalną metodę, nową metodą poprzez zamianę. Wyciąga stary, wkłada nowy. Wszystkie twoje adnotacje z klasy, w której się znajduje, są nietknięte, ponieważ ponieważ podeszliśmy podstępnie z boku.

Teraz mogliśmy po prostu wskoczyć do klasy, której potrzebowaliśmy, użyć tej metody, zrobić to, czego chcemy. Jednak skończylibyśmy z problemami lub popsutymi konstruktorami lub innymi rzeczami. Sposób, w jaki podejmiesz próbę wstrzyknięcia kodu, jest równie ważny, jak cokolwiek, gdy pracujesz z dużym projektem.

Ta odpowiedź mogła być trochę długa, ale InvokationHandler może być początkowo nieco trudny do uchwycenia.

Powiązane problemy