2011-08-04 4 views
11

Hibernate Validator의 메소드 검증 지원을 사용할 수 있도록 현재 기존 JAX/RS 리소스를 프록시하려고합니다. 그러나 (현재 cglib 2.2 사용) 클래스를 프록시 할 때 FormParam 주석이 프록시 클래스의 매개 변수에 존재하지 않으므로 JAX/RS 런타임 (Apache 윙크)이 매개 변수를 채우지 않습니다. 메서드에서 매개 변수 주석을 유지하는 Java에서 동적 프록시를 만들려면 어떻게해야합니까?

 
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 

다른 대안이 있습니까 :

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

이 나에게 다음과 같은 출력을 제공합니다 : 다음은이를 보여줍니다 몇 가지 테스트 코드는?

답변

0

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

향상된 클래스에서 주석을 제거하고 인터페이스에 넣습니다. 인터페이스에 대해 유효성을 검사합니다. 이것은 많은 자원을위한 많은 보일러 - 플레이트 인터페이스 일지 모르지만, 모든 것을 맵핑하여 오브젝트 또는 DTO를 형성하는 것보다 훨씬 더 잘 맞습니다.

+0

나는 자바 프록시로 이것을 시도했지만 특별히 cglib을 시도하지는 않았다. 나는 그것을 시험해보고 그것이 무엇인가를 바꾸는지를 볼 것입니다. 그러나 나는 희망이 없다고 말해야 만합니다. –

+0

나는 이것을 사용해 봤지만 쓸모가 없다 :-(. –

+0

이 스프링 밸리데이터가 인터페이스와 함께 작동하는 경우 시도해 볼 수 있습니까? 그렇다면 클래스에서 주석을 제거하고 인터페이스를 구현하여 유효성 검사기에 넣으시겠습니까? –

1

나는 어쩌면 주석이 프록시 인스턴스에 동적으로 추가되지 않는다고 생각합니다. (아마도 간단하지 않기 때문일 수 있습니다). 그러나 호출 (또는 필터링) 중에 실제 메소드 인스턴스에서 주석을 가져 오는 것이 가능합니다. 예를 들어,

내가 CGLIB 함께 일한 적이
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

그러나 나는와 Javassist 당신이 클래스의 인스턴스를 얻을 수있는 invocationhandlers를 사용할 수 있다는 것을 알고,하고 뭔가를 대상으로 할 수있는 방법의 모든 종류의 주석을 변경하지 않고있다 . 필자가 가장 좋아하는 방법 중 하나는 클래스 내부에 있지 않은 메서드에 연결하는 것이지만 호출하면 해당 클래스 내부의 메서드를 호출 할 때 바이트 코드 수준을 변경하거나 약간 느리지 만 사람이 읽을 수있는 높은 값을 사용할 수 있습니다 레벨 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의 선언 된 메서드에 연결합니다.이 경우에는 코드를 호출 할 때 발생합니다. 그래서 우리는 새로운 Expression Editor를 시작합니다.

그런 다음 Expression Editor는 원래의 구조를 엉망으로 만들고 싶지는 않지만 실제 클래스를 원래의 구조로 만들지 않도록하고, 핵심 클래스를 변경하지 않고 무언가를하고 싶은 클래스 내부의 메소드를 가져옵니다.

if와 goodies로 모두 유효성이 검사되면 메소드는 원래 메소드를 replace를 통해 새 메소드로 바꿉니다. 낡은 것을 꺼내 새 것을 넣습니다. 포함하는 클래스의 모든 주석은 변경되지 않습니다. 왜냐하면 우리가 옆에서 몰래 들어 왔기 때문입니다.

이제 우리는 우리가 필요로하는 클래스에 바로 뛰어 들었을 수 있었고, 그 방법을 강타하고, 우리가 원하는 것을했을 수있었습니다. 그러나 우리는 문제를 일으키거나 생성자 또는 다른 것을 엉망으로 만들었을 것입니다. 코드 삽입에 접근하는 방식은 대규모 프로젝트로 작업 할 때 무엇보다 중요합니다.

이 답변은 다소 길 수도 있지만 InvokationHandler는 처음에는 이해하기가 다소 어려울 수 있습니다.

관련 문제