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 Hibernate, JPA, 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. |
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 y 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.
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 y 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.
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!
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.
ResponderEliminarCreo que has encontrado la motivación para sorprendernos con nuevos post...GENIAL.
Un abrazo
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.
ResponderEliminarFelicidades por el post
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.
ResponderEliminarUn abrazo.
Muy claro el artículo, pero tengo una duda/pregunta/sugerencia/detalle/mejora:
ResponderEliminar¿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.
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.
ResponderEliminarUn abrazo.
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?
ResponderEliminarGracias por el retweet.
@Alber, ¿En que curso coincidimos?
ResponderEliminarHola Gonzalo, pues lo estuve dudando, aunque así dicho parezca increible...
ResponderEliminarDependiendo 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.
@Pronoide "que se puede devolver cualquier cosa"
ResponderEliminarClaro, 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.
¡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é!
ResponderEliminarMuchas gracias.
Gracias Gracias Gracias. Siempre trabajé con proxies pero nunca entendí que estaba haciendo. Fue muy esclarecedor!
ResponderEliminarComo hago para obtener el objeto detras del proxy con spring
ResponderEliminarComo hago para obtener el objeto detras del proxy con spring
ResponderEliminar