Как я могу создать динамический прокси в Java, который сохраняет аннотации параметров для методов? - PullRequest
11 голосов
/ 04 августа 2011

В настоящее время я пытаюсь прокси-сервер некоторых существующих ресурсов 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");
    }
}

Это дает мне следующий вывод:

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

Есть ли другие альтернативы?

Ответы [ 3 ]

1 голос
/ 22 сентября 2012

Подозреваю, аннотации динамически не добавляются в экземпляры прокси.(вероятно, потому что это не просто).Однако можно получить аннотации из фактического экземпляра метода во время вызова (или фильтрации).Например,

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 голосов
/ 05 августа 2017

Я никогда не работал с 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 во время выполнения.Затем он получает метод внутри этого класса.Затем он получает весь classPool каждого класса, который связан с одним экземпляром класса, который мы получили от myPool.Почему это ограничивает объемы, с которыми мы сталкиваемся или можем испортиться, занимает немного памяти во время генерации кода.

Затем он добавляет новый метод в ctCreature, который является классом, который мы инициировали в переменной.раньше сожалею об этом.Метод создается в этом классе, но затем копируется в другой класс, откуда мы хотим его использовать.Затем он подключается к объявленному методу modifyFightSkill, что в данном случае происходит именно тогда, когда мы хотим, чтобы наш код вызывался.Поэтому, когда он запускается, мы запускаем новый редактор выражений.

Этот редактор выражений затем проверяет, что реальный класс, в котором мы хотим быть, но не испортил его первоначальную конструкцию, и получает метод внутри этогокласс, с которым мы хотим что-то сделать, не изменяя его базовый класс.

Как только это произойдет, и все будет проверено с помощью ifs и положительных героев, метод затем заменяет исходный метод нашим новым методом через replace.Вынимает старую, вставляет новую. Все ваши аннотации из класса, в котором они содержатся, не тронуты, потому что мы незаметно вошли со стороны.

Теперь мы могли просто прыгнуть прямо в нужный нам класс, воспользоваться этим методом и сделать то, что мы хотим.Однако мы бы столкнулись с проблемами, или испортили конструкторы или другие вещи.То, как вы подходите к внедрению кода, очень важно при работе с большим проектом.

Возможно, этот ответ был довольно длинным, но InvokationHandler поначалу может быть немного сложно понять.

0 голосов
/ 04 августа 2011

Технически 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.

...