ProgramaciónArquitecto Java

¿Qué es un proxy, tipos de proxy en Java y cómo se implementa el comportamiento dinámico de los objetos a través de él?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia de la cuestión:

Los objetos proxy y el patrón Proxy aparecieron en Java para crear objetos sustitutos que pueden controlar el acceso a un objeto real, modificar su comportamiento o inyectar funciones transversales (seguridad, registro, métricas). Desde Java 1.3, existe soporte para proxy dinámicos estándar en el paquete java.lang.reflect, y más tarde, bibliotecas populares como CGLIB y ByteBuddy.

Problema:

No siempre es posible o conveniente modificar directamente la lógica de una clase existente. A menudo se requiere agregar de manera transparente comportamiento (como registro, almacenamiento en caché, transacciones) sin modificar el código fuente del objeto, lo cual es imposible usando la herencia estándar.

Solución:

Los objetos Proxy pueden implementarse de manera estática (a través de subclassing manual) y de manera dinámica (a través del mecanismo de reflexión o bytecode). Un proxy dinámico permite crear on-the-fly objetos que implementan la interfaz necesaria y delegan las llamadas al objeto real dentro del método invoke(), proporcionando flexibilidad para inyectar comportamiento externo.

Ejemplo de código:

import java.lang.reflect.*; interface Service { void doWork(); } class RealService implements Service { public void doWork() { System.out.println("Haciendo trabajo!"); } } class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Llamando: " + method.getName()); return method.invoke(target, args); } } Service real = new RealService(); Service proxy = (Service) Proxy.newProxyInstance( real.getClass().getClassLoader(), new Class[] {Service.class}, new LoggingInvocationHandler(real)); proxy.doWork(); // Se registra la llamada

Características clave:

  • Los proxies estáticos requieren código predefinido para cada función
  • Los proxies dinámicos pueden crearse sobre la marcha en tiempo de ejecución para cualquier interfaz
  • Para clases sin interfaces se requieren bibliotecas externas (por ejemplo, CGLIB)

Preguntas trampa.

¿Puede un Proxy dinámico estándar en Java trabajar con clases que no implementan interfaces?

No. El Proxy estándar en Java solo funciona con interfaces. Para clases sin interfaces (por ejemplo, si quieres proxy un clase normal) se necesitan herramientas externas (por ejemplo, CGLIB, ByteBuddy).

¿Se puede crear un proxy para una interfaz con métodos privados?

No. Las interfaces en Java no pueden contener métodos privados que necesiten ser proxy. El proxy dinámico implementa los métodos públicos de la interfaz. Desde Java 9 se introdujeron métodos privados por defecto, pero no son accesibles a través del proxy.

¿Cuál es la diferencia entre InvocationHandler y MethodInterceptor (CGLIB)?

InvocationHandler es la interfaz estándar de JDK utilizada para proxies dinámicos. Acepta llamadas a los métodos de la interfaz. MethodInterceptor es una interfaz de CGLIB que permite interceptar llamadas a métodos en cualquier clase a través de la herencia dinámica. La diferencia está en la aplicabilidad y el nivel: JDK solo trabaja con interfaces, CGLIB trabaja con cualquier clase.

Errores comunes y anti-patrones

  • Error de tipo: intentar usar Proxy para una clase que no implementa una interfaz
  • Llamadas cíclicas implícitas que llevan a StackOverflow
  • Proxy de clases que no están destinadas a esto, o intentar proxy clases finales

Ejemplo de la vida real

Caso negativo

Un ingeniero repite manualmente el código de registro en cada método de todos los servicios. Al agregar nuevos métodos, duplica constantemente la lógica, a menudo olvidando agregar las llamadas necesarias.

Ventajas:

  • Transparente, fácil de entender el flujo de ejecución

Desventajas:

  • Mucho tiempo perdido, mucho código repetitivo, difícil de mantener

Caso positivo

Se ha implementado AOP en el proyecto a través de un proxy dinámico: el registro y el control de transacciones se realizan a través de envolturas sustitutas, implementadas de forma centralizada.

Ventajas:

  • Implementación y cambio más rápidos de cualquier funcionalidad transversal, sin duplicación

Desventajas:

  • Necesidad de entender cómo funciona el proxy, posibles dificultades en la depuración