Patrón de Proxy: Implementación Estática y Dinámica en Java

Descripción General

El patrón de proxy es un patrón de diseño estructural que proporciona un sustituto o marcador de posición para otro objeto para controlar el acceso a este. En Java, se caracteriza porque la clase proxy y la clase real implementan la misma interfaz, y la clase proxy delega las operaciones a la instancia de la clase real, pudiendo añadir lógica adicional antes o después de la ejecución del método.

En términos simples, cuando un cliente interactúa con un objeto, lo hace a través de un proxy. Esta indirecta permite incorporar funcionalidades como logging, validación o gestión de recursos sin modificar la clase original.

Proxy Estático vs Dinámico

El proxy estático requiere definir maunalmente una clase proxy para cada clase real, lo que puede generar código repetitivo. En contraste, el proxy dinámico genera las clases proxy en tiempo de ejecución, ofreciendo mayor flexibilidad. Las implementaciones más comunes en Java son el proxy dinámico basado en JDK y el basado en CGLib.

Proxy Dinámico con JDK

Esta técnica utiliza las clases java.lang.reflect.Proxy y java.lang.reflect.InvocationHandler. Para crear un proxy, se sigue un proceso donde se define un manejador de invocación que intercepta las llamadas a métodos de la interfaz.

Pasoss para crear un proxy JDK:

  1. Implementar la interfaz InvocationHandler para manejar la lógica de interceptación.
  2. Usar el método estático Proxy.newProxyInstance para generar la instancia del proxy.

Ejemplo de código con JDK Dynamic Proxy:

// Interfaz común
interface Operacion {
    void ejecutar();
}

// Clase real
class Cliente implements Operacion {
    private String identificador;

    public Cliente(String id) {
        this.identificador = id;
    }

    @Override
    public void ejecutar() {
        System.out.println(identificador + " está procesando la tarea.");
    }
}

// Manejador de invocación
class ProxyHandler implements InvocationHandler {
    private Object objetoReal;

    public ProxyHandler(Object target) {
        this.objetoReal = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Interceptando método: " + method.getName());
        // Lógica adicional antes
        Thread.sleep(1500);
        Object resultado = method.invoke(objetoReal, args);
        // Lógica adicional después
        System.out.println("Método finalizado.");
        return resultado;
    }
}

// Prueba de uso
class DemoProxyJDK {
    public static void main(String[] args) {
        Cliente cliente = new Cliente("ABC-123");
        InvocationHandler handler = new ProxyHandler(cliente);
        Operacion proxy = (Operacion) Proxy.newProxyInstance(
            Operacion.class.getClassLoader(),
            new Class[]{Operacion.class},
            handler
        );
        proxy.ejecutar();
    }
}

El proxy JDK solo puede proxyizar interfaces, ya que la clase proxy generada hereda de Proxy e implementa las interfaces proporcionadas, respetando la herencia simple de Java.

Proxy Dinámico con CGLib

CGLib (Code Generation Library) permite proxyizar clases concretas al generar subclases en tiempo de ejecución. Sus componentes clave son Enhancer e MethodInterceptor.

Ejemplo de código con CGLib:

// Clase concreta
class Empleado {
    private String nombre;
    private int edad;

    public void trabajar(String tarea, int horas) {
        System.out.println(nombre + " trabaja en " + tarea + " por " + horas + " horas.");
    }

    public final void descansar() {
        System.out.println("Descanso obligatorio.");
    }
}

// Interceptador de métodos
class LogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Iniciando proxy CGLib para: " + method.getName());
        Thread.sleep(2000);
        Object resultado = proxy.invokeSuper(obj, args);
        System.out.println("Proxy CGLib finalizado.");
        return resultado;
    }
}

// Prueba de uso
class DemoProxyCGLib {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Empleado.class);
        enhancer.setCallback(new LogInterceptor());
        Empleado proxy = (Empleado) enhancer.create();
        proxy.trabajar("Desarrollo", 8);
        proxy.descansar(); // Este método no será interceptado por ser final.
    }
}

CGLib no puede interceptar métodos declarados como final en la clase original, ya que la subclase generada hereda dichos métodos sin modificaciones.

Consideraciones Prácticas

En proyectos modernos, frameworks como Spring Boot 2.x utilizan CGLib por defecto para la proxyización de beans, permitiendo así cubrir tanto clases como interfaces. La elección entre JDK y CGLib depende de si se necesita proxyizar una interfaz o una clase concreta.

Etiquetas: java Proxy Pattern JDK Dynamic Proxy CGLib Reflection

Publicado el 6-12 16:19