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

Согласно документации:

[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, которые могут генерировать динамические прокси-серверы, которые наследуются от определенного абстрактного класса, перенаправляя все вызовы методам abstract обработчику вызова?

Например, предположим, что у меня есть абстрактный класс 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

Ответ 1

Это можно сделать, используя Javassist (см. ProxyFactory) или CGLIB.

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

I (Adam Paynter) написал этот код с помощью 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();

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

Woof!
Handling public abstract void mock.Dog.fetch() via the method handler

Ответ 2

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

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

Если я возьму в качестве примера ваш класс собаки, ваш обработчик вызова вызовет метод может выглядеть следующим образом (с существующим связанным подклассом собак, который называется.. well... 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, вы можете обнаружить (в Proxy.java:362), что невозможно создать прокси для объекта класса, который не представляет собой интерфейс).

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