Альтернативи 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: Саме те, що мені потрібно! Я відредагую вашу відповідь за допомогою мого зразкового коду.
Адам Пейнтер,

proxyFactory.setHandler()застаріло. Будь ласка, використовуйте proxy.setHandler.
AlikElzin-kilaka

@axtavt - це об'єкт "Dog" реалізація чи інтерфейс у наведеному вище коді?
stackoverflow

-7

Що ви можете зробити в такому випадку, це мати обробник проксі, який буде перенаправляти виклики до існуючих методів вашого абстрактного класу.

Звичайно, вам доведеться його кодувати, проте це досить просто. Для створення свого проксі-сервера вам доведеться дати йому InvocationHandler. Тоді вам доведеться лише перевірити тип методу в 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змінної для виклику методів ... Вибачте, якщо я заплутаний, мені доведеться ще трохи подумати над цим.
Адам Пейнтер,

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

1
@Andrzej: Я розумію, що те, про що я прошу, вимагає маніпулювання байт-кодом (насправді я вже написав рішення своєї проблеми за допомогою ASM). Я також розумію, що динамічні проксі Java підтримують лише інтерфейси. Можливо, моє запитання було не зовсім зрозумілим - я запитую, чи є якийсь інший клас (тобто щось інше, ніж java.lang.reflect.Proxy), який робить те, що мені потрібно.
Адам Пейнтер,

2
Ну, щоб скоротити довгі речі ... ні (принаймні в стандартних класах Java). Використовуючи маніпуляції з байт-кодом, небо - це межа!
Riduidel

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