Alternativas a java.lang.reflect.Proxy para crear proxies de clases abstractas (en lugar de interfaces)

89

Según la documentación :

[ java.lang.reflect.] Proxyproporciona métodos estáticos para crear instancias y clases de proxy dinámicos, y también es la superclase de todas las clases de proxy dinámicas creadas por esos métodos.

El newProxyMethodmétodo (responsable de generar los proxies dinámicos) tiene la siguiente firma:

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

Desafortunadamente, esto evita que uno genere un proxy dinámico que amplíe una clase abstracta específica (en lugar de implementar interfaces específicas). Esto tiene sentido, considerando que java.lang.reflect.Proxyes "la superclase de todos los proxies dinámicos", evitando así que otra clase sea la superclase.

Por lo tanto, ¿hay alguna alternativa java.lang.reflect.Proxyque pueda generar proxies dinámicos que hereden de una clase abstracta específica, redirigiendo todas las llamadas a los métodos abstractos al controlador de invocación?

Por ejemplo, supongamos que tengo una clase abstracta Dog:

public abstract class Dog {

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

    public abstract void fetch();

}

¿Existe alguna clase que me permita hacer lo siguiente?

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
Adam Paynter
fuente

Respuestas:

123

Se puede hacer usando Javassist (ver ProxyFactory) o CGLIB .

El ejemplo de Adam usando Javassist:

Yo (Adam Paynter) escribí este código usando 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();

Que produce esta salida:

¡Guau!
Manejo de mock.Dog.fetch () de vacío abstracto público a través del controlador de métodos
axtavt
fuente
10
+1: ¡Exactamente lo que necesito! Editaré tu respuesta con mi código de muestra.
Adam Paynter
proxyFactory.setHandler()es obsoleto. Utilice proxy.setHandler.
AlikElzin-kilaka
@axtavt ¿el objeto "Perro" es una implementación o una interfaz en el código anterior?
stackoverflow
-7

Lo que puede hacer en tal caso es tener un controlador de proxy que redirigirá las llamadas a los métodos existentes de su clase abstracta.

Por supuesto, tendrá que codificarlo, sin embargo, es bastante simple. Para crear su Proxy, tendrá que darle un InvocationHandler. Luego, solo tendrá que verificar el tipo de método en el invoke(..)método de su controlador de invocación. Pero tenga cuidado: tendrá que comparar el tipo de método con el objeto subyacente asociado a su controlador, y no con el tipo declarado de su clase abstracta.

Si tomo como ejemplo su clase de perro, el método de invocación de su controlador de invocación puede verse así (con una subclase de perro asociada existente llamada .. bueno ... 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 ?
    }
}

Sin embargo, hay algo que me deja pensando: he hablado de un dogobjeto. Pero, como la clase Dog es abstracta, no puede crear instancias, por lo que tiene subclases existentes. Además, como revela una inspección rigurosa del código fuente de Proxy, puede descubrir (en Proxy.java:362) que no es posible crear un Proxy para un objeto Class que no representa una interfaz).

Entonces, más allá de la realidad , lo que quieres hacer es perfectamente posible.

Riduidel
fuente
1
Por favor, tengan paciencia conmigo mientras trato de entender su respuesta ... En mi caso particular, quiero que la clase de proxy (generada en tiempo de ejecución) sea la subclase de Dog(por ejemplo, no estoy escribiendo explícitamente una Poodleclase que implemente fetch()). Por lo tanto, no hay ninguna dogvariable sobre la que invocar los métodos ... Lo siento si me confundo, tendré que pensarlo un poco más.
Adam Paynter
1
@Adam: no puede crear subclases dinámicamente en tiempo de ejecución sin alguna manipulación del código de bytes (CGLib creo que hace algo como esto). La respuesta corta es que los proxies dinámicos admiten interfaces, pero no clases abstractas, porque los dos son conceptos muy diferentes. Es casi imposible pensar en una forma de proxy dinámico de clases abstractas de una manera sensata.
Andrzej Doyle
1
@Andrzej: Entiendo que lo que estoy pidiendo requiere manipulación del código de bytes (de hecho, ya escribí una solución a mi problema usando ASM). También entiendo que los proxies dinámicos de Java solo admiten interfaces. Quizás mi pregunta no estaba del todo clara: estoy preguntando si hay alguna otra clase (es decir, algo diferente a java.lang.reflect.Proxy) disponible que haga lo que necesito.
Adam Paynter
2
Bueno, para abreviar las cosas largas ... no (al menos en las clases estándar de Java). Usando la manipulación del código de bytes, ¡el cielo es el límite!
Riduidel
9
Voté en contra porque no es realmente una respuesta a la pregunta. OP declaró que quiere proxy de una clase, no de una interfaz, y es consciente de que eso no es posible con java.lang.reflect.Proxy. Simplemente repite ese hecho y no ofrece otra solución.
jcsahnwaldt Reincorporación a Monica