Java Dynamic Proxy

Los proxy dinámicos fueron introducidos en la API de Java 1.3 agregando múltiples funcionalidades a la plataforma Java. En este post pretendo enseñarles algunos usos de los proxy dinámicos y cómo implementar uno sencillo.

Primero tienen que conocer la definición de proxy y sus posibles usos, que ya fueron descritas en un post anterior, puedes revisarlo antes de continuar: Patrones de Diseño: Proxy

Primero debemos conocer las clases que hacen posible la implementación de Dynamic Proxy en la API de Java, estas son java.lang.reflect.Proxy y java.lang.reflect.InvocationHandler, además debes tener conceptos básicos de Reflexión (sólo para la invocación de métodos).

java.lang.reflect.Proxy

Para crear un objeto Proxy debes utilizar la clase java.lang.reflect.Proxy de la siguiente forma:

1
Object obj = Proxy.newProxyInstance(classLoader, interfaces, handler);

Donde:

  • classLoader: classloader que contiene la definición de las interfaces que componen el objeto target (o RealSubject).
  • interfaces: Arreglo con la definición de las interfaces que implementa el objeto target (o RealSubject).
  • handler: Esta es la clase que nos permitirá implementar el Proxy, es la clase que contiene la lógica de negocio del Proxy.

java.lang.reflect.InvocationHandler

Esta interfaz debe ser implementada y tenemos el método invoke que hará las veces de interceptor, es aquí donde debemos escribir nuestra lógica de negocio.

1
2
3
4
5
public interface InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

Reglas para crear un Dynamic Proxy

  • El objeto a “proxiar” debe tener una interfaz, no una clase concreta o abstracta, una Interfaz.
  • El arreglo de Interfaces no debe contener interfaces duplicadas.
  • Todas las interfaces deben ser visibles en el classloader que se esta utilizando (usado en la construcción del Proxy).
  • Las interfaces del proxy no deben tener conflictos de métodos. Ver la especificación de Lenguaje Java sobre sobre-escritura de métodos.

Ejemplo Práctico

Veamos un ejemplo práctico de la implementación de un proxy dinámico usando la API de Java:

1
2
3
4
5
6
7
8
9
10
public interface Calculadora {

/**
* @param a
* @param b
* @return suma de a+b
*/
Integer suma(Integer a, Integer b);

}

Ahora veamos la implementación de Calculadora:

1
2
3
4
5
6
7
8
public class CalculadoraImpl implements Calculadora {

@Override
public Integer suma(Integer a, Integer b) {
return a + b;
}

}

Supongamos que estas clases ya están creadas y necesitamos agregar un par de funcionalidades sin tener que tocar el código ya existente. La primera funcionalidad es validar que los números a y b sean mayores a 0 y la segunda funcionalidad es loggear los parámetros de entrada.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class CalculadoraProxyFactory implements InvocationHandler {

private final Calculadora _target;

/**
* @param target
*/
public CalculadoraProxyFactory(Calculadora target) {
_target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
for (Object object : args) {
if (object instanceof Integer) {
Integer number = Integer.valueOf(object.toString());

if (number.intValue() > 0) {
System.out.println("Parameter [" + number + "]");
} else {
System.err.println("Invalid number [" + number + "]");
throw new RuntimeException("Invalid number [" + number
+ "]");
}
} else {
System.err.println("Invalid type");
throw new RuntimeException("Invalid type");
}
}
System.out.println("Execute method [" + method.getName() + "]");
return method.invoke(_target, args);
}

/**
* Factory para crear proxy de Calculadora
*
* @param target
* @return proxy de Calculadora
*/
public static Calculadora proxyFactory(Calculadora target) {
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return (Calculadora) Proxy.newProxyInstance(classLoader, interfaces,
new CalculadoraProxyFactory(target));
}
}

Como podrán ver en el ejemplo, en el método invoke se hace el control y el log sobre los argumentos que entran al método suma del RealSubject. Además para simplificar la creación del Proxy, se implementó un method-factory que crea el proxy dinámico (método proxyFactory).

Ahora el test unitario que prueba el funcionamiento del Proxy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class CalculadoraProxyFactoryTest {

private Calculadora _calculadoraProxy;

@Before
public void before() {
System.out.println("------------------------------");
_calculadoraProxy = CalculadoraProxyFactory
.proxyFactory(new CalculadoraImpl());
}

@Test
public void shouldWork() throws Exception {
Integer result = _calculadoraProxy.suma(1, 2);
assertNotNull(result);
assertEquals(Integer.valueOf(3), result);
}

@Test(expected = RuntimeException.class)
public void failWrongNumber() throws Exception {
_calculadoraProxy.suma(-1, 2);
}

@Test(expected = RuntimeException.class)
public void failNullNumber() throws Exception {
_calculadoraProxy.suma(null, 2);
}
}

La salida de este test a la consola con maven es la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running patterns.dynamicproxy.CalculadoraProxyFactoryTest
------------------------------
Invalid number [-1]
------------------------------
Invalid type
------------------------------
Parameter [1]
Parameter [2]
Execute method [suma]

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.045 sec

Espero que les haya sido útil, en un próximo post escribiré sobre otras formas de crear Proxy. Deje su comentario.

Author

Francisco Collao

Posted on

2013-07-21

Updated on

2023-06-07

Licensed under

Comentarios