Java Dynamic Proxy

3 minuto de lectura

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:

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.

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:

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:

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.

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:

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:

-------------------------------------------------------
 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.

Comentar