Альтернативы java.lang.reflect.Proxy для создания прокси абстрактных классов (а не интерфейсов)


89

По документации :

[ java.lang.reflect.] Proxyпредоставляет статические методы для создания динамических прокси-классов и экземпляров, а также является суперклассом всех динамических прокси-классов, созданных этими методами.

newProxyMethodМетод (отвечает за генерацию динамических прокси) имеет следующую подпись:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                             throws IllegalArgumentException

К сожалению, это не позволяет генерировать динамический прокси, который расширяет определенный абстрактный класс (а не реализует определенные интерфейсы). Это имеет смысл, учитывая, что java.lang.reflect.Proxyэто «суперкласс всех динамических прокси», тем самым предотвращая то, что другой класс является суперклассом.

Следовательно, есть ли какие-либо альтернативы тому, java.lang.reflect.Proxyчто может генерировать динамические прокси, которые наследуются от определенного абстрактного класса, перенаправляя все вызовы абстрактных методов на обработчик вызова?

Например, предположим, что у меня есть абстрактный класс Dog:

public abstract class Dog {

    public void bark() {
        System.out.println("Woof!");
    }

    public abstract void fetch();

}

Есть ли класс, который позволяет мне делать следующее?

Dog dog = SomeOtherProxy.newProxyInstance(classLoader, Dog.class, h);

dog.fetch(); // Will be handled by the invocation handler
dog.bark();  // Will NOT be handled by the invocation handler

Ответы:


123

Это можно сделать с помощью Javassist (см. ProxyFactory) Или CGLIB .

Пример Адама с использованием Javassist:

Я (Адам Пэйнтер) написал этот код с помощью Javassist:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Dog.class);
factory.setFilter(
    new MethodFilter() {
        @Override
        public boolean isHandled(Method method) {
            return Modifier.isAbstract(method.getModifiers());
        }
    }
);

MethodHandler handler = new MethodHandler() {
    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        System.out.println("Handling " + thisMethod + " via the method handler");
        return null;
    }
};

Dog dog = (Dog) factory.create(new Class<?>[0], new Object[0], handler);
dog.bark();
dog.fetch();

Что производит этот вывод:

Гав!
Обработка общедоступного абстрактного void mock.Dog.fetch () через обработчик метода

10
+1: Именно то, что мне нужно! Я отредактирую ваш ответ своим образцом кода.
Adam Paynter

proxyFactory.setHandler()не рекомендуется. Пожалуйста, используйте proxy.setHandler.
AlikElzin-kilaka

@axtavt является ли объект "Собака" реализацией или интерфейсом в приведенном выше коде?
stackoverflow

-7

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

Вам, конечно, придется его закодировать, но это довольно просто. Для создания вашего прокси вам нужно предоставить ему InvocationHandler. После этого вам нужно будет только проверить тип метода в invoke(..)методе вашего обработчика вызова. Но будьте осторожны: вам придется проверять тип метода по базовому объекту, связанному с вашим обработчиком, а не по объявленному типу вашего абстрактного класса.

Если я возьму в качестве примера ваш класс собаки, метод invoke вашего обработчика вызовов может выглядеть следующим образом (с существующим связанным подклассом собаки, который называется ... ну ... dog)

public void invoke(Object proxy, Method method, Object[] args) {
    if(!Modifier.isAbstract(method.getModifiers())) {
        method.invoke(dog, args); // with the correct exception handling
    } else {
        // what can we do with abstract methods ?
    }
}

Однако есть кое-что, что заставляет меня задуматься: я говорил об dogобъекте. Но поскольку класс Dog является абстрактным, вы не можете создавать экземпляры, поэтому у вас есть существующие подклассы. Более того, как показывает тщательная проверка исходного кода прокси, вы можете обнаружить (на Proxy.java:362), что невозможно создать прокси для объекта класса, который не представляет интерфейс).

Итак, помимо реальности , то, что вы хотите делать, вполне возможно.


1
Пожалуйста, терпите меня, пока я пытаюсь понять ваш ответ ... В моем конкретном случае я хочу, чтобы прокси-класс (сгенерированный во время выполнения) был подклассом Dog(например, я явно не пишу Poodleкласс, который реализует fetch()). Следовательно, нет dogпеременной для вызова методов ... Извините, если я запутался, мне придется подумать над этим еще немного.
Adam Paynter

1
@Adam - вы не можете динамически создавать подклассы во время выполнения без некоторых манипуляций с байт-кодом (CGLib, я думаю, делает что-то вроде этого). Короткий ответ заключается в том, что динамические прокси-серверы поддерживают интерфейсы, но не абстрактные классы, потому что это очень разные концепции. Практически невозможно придумать способ динамического проксирования абстрактных классов разумным способом.
Анджей Дойл

1
@Andrzej: Я понимаю, что то, о чем я прошу, требует манипуляции с байт-кодом (на самом деле, я уже написал решение своей проблемы с помощью ASM). Я также понимаю, что динамические прокси Java поддерживают только интерфейсы. Возможно, мой вопрос не совсем ясен - я спрашиваю, есть ли какой-нибудь другой класс (то есть что-то кроме java.lang.reflect.Proxy), который делает то, что мне нужно.
Adam Paynter

2
Короче говоря ... нет (по крайней мере, в стандартных классах Java). Использование байт-кода - это предел!
Riduidel

9
Я проголосовал против, потому что это не совсем ответ на вопрос. OP заявил, что он хочет проксировать класс, а не интерфейс, и знает, что это невозможно с java.lang.reflect.Proxy. Вы просто повторяете этот факт и не предлагаете другого решения.
jcsahnwaldt Reinstate Monica
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.