martes, 7 de septiembre de 2010

Los proxies en Java, Dynamic Proxies vs CGlib, y su uso desde Spring

Empiezo con este artículo a comentar cosas que normalmente llaman la atención cuando las nombro en clase, son temas que cuando se pronuncian el mundo asiente, pero que sinceramente creo casi todos flojeamos con ellas. En esta línea de artículos pienso hablar de conceptos que muchas veces yo doy por sabidos en los cursos. Así podré referenciar a mi propio blog para explicar aspectos que muchas veces no caben en los temarios, ¡jejeje! (malévolo).

Además este es el primer artículo técnico que escribo, los anteriores (y de los que habrá más) han sido de opinión/valoración.


Los Proxies

Todos los días los programadores Java usamos proxies de manera consciente o inconsciente: por ejemplo, en las llamadas remotas vía RMI, o con los componentes EJB, o con los servicios web vía JAX-RPC, incluso cuando usamos HibernateJPA, o en ocasiones Spring AOP, estamos haciendo uso de clases proxy, estos proxies nos hacen el "trabajo sucio" para que nuestro código no tenga que ocuparse de tales tareas, es decir se encargan de serializar-deserializar objetos, abrir-cerrar transacciones, etc.

Pero, ¿Qué es un Proxy exactamente?

Las clases proxy son como
 los dobles de cine.
Una clase Proxy o Delegada es simplemente una clase que implementa los métodos de otra, como si fuera un doble de cine, que asume responsabilidades en nombre de otra clase. Un proxy permite implementar multitud de patrones de diseño de una aplicación de forma más natural y sencilla. Realmente lo que creamos es un objeto doble de otro objeto en memoria.

Vale,  ¿Cómo se construyen?

Normalmente los entornos de desarrollo nos permiten crear clases nuevas que tengan los mismos métodos que otra dada y realizar la retrollamada automática o callback, por ejemplo, en eclipse esto se consigue con el menú Source/Generate Delegate Methods de la perspectiva Java.

Veamos un ejemplo:


Creo que el código anterior no necesita explicación ninguna...

¿Y no se puede hacer mejor?

Sí, ciertamente crear el código fuente de la clase del Proxy "a mano" con o sin ayuda de un IDE es un poco molesto y poco productivo, aunque precisamente eso era lo que hacíamos cuando usábamos herramientas  como el compilador rmic de java hasta la versión 1.4 o al desplegar un módulo ejb-jar 2.x.

Existen dos alternativas para no tener que hacer este trabajo por adelantado en nuestro código fuente: la clases para crear Proxies Dinámicos del paquete Reflection (java.lang.reflect) y la librería para generación de código CGlib:


Dynamic Proxies

A partir de JSE 1.3 incluido en el API de Reflection, Java trae la funcionalidad de crear Dynamic Proxies, es decir clases que se generan dinámicamente para suplantar/modificar/ampliar el comportamiento de otras, veamos el código:


Aquí si tenemos cosas que comentar:

1) Para crear un doble (un proxy dinámico) sólo necesitamos llamar a la función Proxy.newProxyInstance, esta función recibe 3 parámetros:
  • el cargador de clases de la clase de la que queremos hacer el proxy.
  • un array con la interfaz o interfaces que contienen los métodos que queremos interceptar de la clase actor.
  • el objeto que recibirá la retrollamada por parte del proxy, en nuestro caso el doble.

¡Et voilà! automáticamente se invocará la función InvocationHandler.invoke de nuestro objeto doble con las llamadas a las funciones del las interfaces introducidas como segundo parámetro de función newProxyInstance.

Esto permite poder construir clases proxies de cualquier clase que implemente al menos una interfaz. pero ¿y si nuestra clase actor no implementará ninguna interfaz...? entonces tendremos que recurrir a una librería llamada cglib.


CGLib

Este framework realmente es una librería de macros de ASM, que es una librería de manipulación de "bytecode", es decir, de código máquina java. Dentro de la CGLib se pueden encontrar (aunque muy mal documentadas) diversas funciones de utilidad, en nuestro caso vamos a usar una clase llamada net.sf.cglib.proxy.Enhancer 

Veamos el código:


Inspeccionando el código, descubrimos que no hace falta ninguna interfaz para usar la clase Enhancer sólo la programamos para funcionar con los métodos setSuperclass setCallback. Bastante fácil, ¿no?, no es de extrañar que Hibernate e iBatis estén escritos con esta librería de proxies. ¿Cual es mejor? unos hablan del rendimiento algo más lento de los proxies dinámicos, otros hablan de las dependencías con librerías (jars) que impone la CGLib. 

Para mi la diferencia entre ambas es algo más sutil, con los proxies dinámicos el proxy se crea partiendo de una instancia de una clase, con la CGlib el proxy se crea a partir de una clase no de una instancia, de forma que para crear un proxy de un objeto concreto con la cglib hay que copiar los valores de los atributos del objeto molde después de crear el proxy, esto suena a Commons BeanUtils, una de las dependencias de Hibernate...


Spring ProxyFactoryBean

Para acabar este "ladrillo" diré que si usamos Springframework, la creación de proxies se ha automatizado con una clase llamada org.springframework.aop.framework.ProxyFactoryBean que permite crear e inyectar dependencias a nuestro código de objetos que sean proxies creados tanto  con el api de reflection  como con la cglib controlando el propiedad proxyTargetClass de dicha clase, aunque en las últimas versiones de Spring la propia existencia o no de una interfaz a la hora de crear el proxy es lo que determina por defecto el comportamiento de esta clase.

Veamos el ejemplo como colofón:


Espero haber aclarado algo a alguien a estas alturas del post sobre el fantástico mundo de los proxies en Java.

¡Hasta la próxima!


13 comentarios:

  1. Pues a mi me has aclarado un montón de dudas. Genial los trozos de código y las aclaraciones oportunas. Creo que volveré a leer el artículo otra vez para terminar de aclarar cosas.

    Creo que has encontrado la motivación para sorprendernos con nuevos post...GENIAL.

    Un abrazo

    ResponderEliminar
  2. Hombre, un post de proxies dinámicos, que bien. Es un tema que a mucha gente le da miedo y la verdad es que son muy útiles. Yo usé hace unos años el CGLib y la verdad es que iba muy bien.
    Felicidades por el post

    ResponderEliminar
  3. Gracias Fernando, muy interesante tu articulo sobre este mundo que todos sabemos que existe, pero pocos sabeis como funciona. Estaré pendiente de tus cursos por si puedo asistir a otro.

    Un abrazo.

    ResponderEliminar
  4. Muy claro el artículo, pero tengo una duda/pregunta/sugerencia/detalle/mejora:

    ¿No sería más lógico que DameDoble o la Factoría devolvieran un Actuar (o un Actor) directamente en lugar de un Object?

    Para el código cliente debería ser totalmente transparente y hacer el casting desde Object no creo que sea responsabilidad suya.

    ResponderEliminar
  5. Si Isra, hablar sobre aspectos curiosos de Java, desarrollar ejemplos de Scala y dar mi opinión sobre ciertos temas son hoy por hoy las líneas que voy a llevar en el blog.

    Un abrazo.

    ResponderEliminar
  6. Hola eamodeorubio, ciertamente a es un tema tabú para muchos. Mi experiencia con la CGlib también ha sido buena. ¿encontraste alguna vez buena documentación?

    Gracias por el retweet.

    ResponderEliminar
  7. Hola Gonzalo, pues lo estuve dudando, aunque así dicho parezca increible...

    Dependiendo de la librería que usemos hay que devolver la interfaz (Actuar) con los proxies dinámicos y la clase (Actor) con la CGLib. No se puede hacer cualquier casting arbitrariamente.

    Sin embargo, dejé como Object el valor de retorno por subrayar que se puede devolver cualquier cosa y no ligar la codificación del proxy a la de las clases que intercepta. Pero quizás si ha quedado algo más feo así que de la forma que propones.

    Muchas gracias por la observación.

    ResponderEliminar
  8. @Pronoide "que se puede devolver cualquier cosa"

    Claro, pero el problema es que no estás llegando a hacer el caso tan genérico. Quiero decir, si encapsulamos la "proxificación" de forma genérica, en una clase Proxificador que recibe cualquier tipo de Objeto y te devuelve un Proxy a ese Objeto, entonces bien, se puede devolver cualquier cosa. (Aunque tengo mis dudas de que algo tan genérico sea muy útil, pero esa es otra historia)

    El problema es que aquí, sigues presentando el ejemplo del Doble. Es decir, defines un DobleDynamicProxy con un método dameElDoble. Es decir, parecería un tanto extraño esperar que DobleDynamicProxy#dameElDoble me dé un... Patinete.

    ResponderEliminar
  9. ¡Gonzalo de acuerdo!, no puedo con lo del patinete, es irrefutable. Además sólo por el interés que te has tomado... ¡lo cambiaré!

    Muchas gracias.

    ResponderEliminar
  10. Gracias Gracias Gracias. Siempre trabajé con proxies pero nunca entendí que estaba haciendo. Fue muy esclarecedor!

    ResponderEliminar
  11. Como hago para obtener el objeto detras del proxy con spring

    ResponderEliminar
  12. Como hago para obtener el objeto detras del proxy con spring

    ResponderEliminar