Introducción al Patrón Proxy
El patrón de diseño Proxy actúa como un intermediario o sustituto de otro objeto para controlar el acceso a este. Al utilizar un proxy, podemos añadir funcionalidades adicionales, como seguridad, registro de eventos o gestión de transaciones, sin modificar la lógica del objeto originla.
Ventajas Principales
- Desacoplamiento: Permite separar las tareas administrativas o transversales (logging, auditoría) de la lógica de negocio principal.
- Mantenibilidad: Facilita la extensión del comportamiento del sistema mediante la gestión centralizada de servicios comunes.
- Control de acceso: El proxy puede decidir si el objeto real debe ser invocado basándose en permisos o estados previos.
Limitaciones del Proxy Estático
En un proxy estático, cada clase de negocio requiere su propia clase proxy correspondiente. Esto genera una duplicación excesiva de código y reduce la eficiencia del desarrollo, ya que cualquier cambio en la interfaz obliga a modificar tanto la implementación real como el proxy.
Proxy Estático: Un Enfoque Manual
Para ilustrar el concepto, consideremos una interfaz de servicio simple y su implementación:
// Interfaz que define el contrato
public interface ServicioEnvio {
void enviarPaquete();
}
// Implementación de la lógica real
public class ProveedorLogistica implements ServicioEnvio {
@Override
public void enviarPaquete() {
System.out.println("Realizando la entrega física del paquete...");
}
}
// Proxy estático que añade lógica adicional
public class ProxyLogistica implements ServicioEnvio {
private final ProveedorLogistica proveedor;
public ProxyLogistica(ProveedorLogistica proveedor) {
this.proveedor = proveedor;
}
@Override
public void enviarPaquete() {
validarDireccion();
proveedor.enviarPaquete();
generarFactura();
}
private void validarDireccion() {
System.out.println("Verificando coordenadas de destino...");
}
private void generarFactura() {
System.out.println("Emitiendo comprobante de envío...");
}
}
Proxy Dinámico: Flexibilidad en Tiempo de Ejecución
A diferencia del estático, el proxy dinámico se genera durante la ejecución del programa. En Java, esto se logra principalmente de dos maneras:
- JDK Dynamic Proxy: Basado en interfaces (estándar de Java).
- CGLIB: Basado en la herencia de clases (común en frameworks como Spring).
Implementación con JDK InvocationHandler
Para crear un proxy dinámico utilizando el JDK, es necesario implementar la interfaz InvocationHandler. Esta clase centraliza todas las llamadas a los métodos del objeto original.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class GestorInvocacionGenerico implements InvocationHandler {
private Object instanciaReal;
public void configurarObjetivo(Object objetivo) {
this.instanciaReal = objetivo;
}
// Método para generar la instancia del proxy de forma dinámica
public Object obtenerProxy() {
return Proxy.newProxyInstance(
instanciaReal.getClass().getClassLoader(),
instanciaReal.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method metodo, Object[] argumentos) throws Throwable {
preprocesar(metodo.getName());
// Ejecución del método en el objeto real
Object resultado = metodo.invoke(instanciaReal, argumentos);
postprocesar(metodo.getName());
return resultado;
}
private void preprocesar(String nombreMetodo) {
System.out.println("[LOG]: Iniciando ejecución de -> " + nombreMetodo);
}
private void postprocesar(String nombreMetodo) {
System.out.println("[LOG]: Finalizada la ejecución de -> " + nombreMetodo);
}
}
Uso del Proxy Dinámico
El cliente interactúa con el proxy de la misma forma que lo haría con el objeto real, pero sin conocer la existencia del intermediario dinámico:
public class SistemaPrincipal {
public static void main(String[] args) {
// Objeto real
ServicioEnvio proveedor = new ProveedorLogistica();
// Configuración del manejador dinámico
GestorInvocacionGenerico manejador = new GestorInvocacionGenerico();
manejador.configurarObjetivo(proveedor);
// Creación del proxy
ServicioEnvio proxy = (ServicioEnvio) manejador.obtenerProxy();
// Ejecución
proxy.enviarPaquete();
}
}
Este enfoque resuelve el problema de la duplicación de código, ya que un solo InvocationHandler puede gestionar múltiples interfaces y clases, permitiendo una arqutiectura de software mucho más limpia y orientada a aspectos (AOP).