Как я могу создать динамический прокси в java, который сохраняет аннотации параметров для методов?

В настоящее время я пытаюсь проксировать некоторые существующие ресурсы JAX/RS, чтобы позволить мне использовать поддержку проверки метода Hibernate Validator. Однако, когда я проксирую свой класс (в настоящее время использую cglib 2.2), аннотация FormParam отсутствует в параметрах в прокси-классе, поэтому среда выполнения JAX/RS (подмигивание apache) не заполняет параметры. Вот некоторый тестовый код, который показывает это:

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

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


person Peter Hart    schedule 04.08.2011    source источник


Ответы (3)


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

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);
    }
}
person Mohan    schedule 21.09.2012

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

person Stefan Schubert-Peters    schedule 04.08.2011
comment
Я пробовал это с прокси-серверами java, но специально не пробовал для cglib. Я попробую и посмотрю, изменится ли что-нибудь. Я должен сказать, что я не надеюсь, однако. - person Peter Hart; 04.08.2011
comment
Можете ли вы попробовать, работает ли этот валидатор Spring с интерфейсами вообще? Так удалить аннотации из класса, реализовать интерфейс и поместить его в валидатор? - person Stefan Schubert-Peters; 05.08.2011

Я никогда не работал с cglib, однако я знаю, что с Javassist вы можете использовать обработчики вызовов для получения экземпляра класса, а затем есть всевозможные способы нацеливания на что-то без изменения его аннотаций. Один из моих любимых способов - подключиться к методу, который не находится внутри этого класса, но вызывает его, а затем, когда он вызывает метод внутри этого класса, он может изменить уровень байт-кода или использовать немного более медленный, но понятный для человека высокий уровень. level 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 и положительных качеств, метод затем заменит исходный метод нашим новым методом через замену. Вынимает старый, вставляет новый. Все ваши аннотации из содержащего класса остаются нетронутыми, потому что мы проникли украдкой со стороны.

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

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

person Xype    schedule 05.08.2017