¿Cuál es la diferencia entre el proxy dinámico JDK y CGLib?

147

En el caso del patrón de diseño de proxy , ¿cuál es la diferencia entre el proxy dinámico de JDK y las API de generación de código dinámico de terceros como CGLib ?

¿Cuál es la diferencia entre usar ambos enfoques y cuándo debería uno preferir uno sobre otro?

KDjava
fuente
3
Obtenga el código aquí: < gist.github.com/ksauzz/1563486 >. En cglib puede crear tanto el proxy de clase como el proxy de interfaz. Spring usa CGlib de forma predeterminada, mientras que AspectJ usa proxy de Java. Lea esto también: jnb.ociweb.com/jnb/jnbNov2005.html ;)
Subhadeep Ray

Respuestas:

185

El proxy dinámico JDK solo puede usar proxy por interfaz (por lo que su clase objetivo necesita implementar una interfaz, que luego también implementa la clase proxy).

CGLIB (y javassist) puede crear un proxy subclasificando. En este escenario, el proxy se convierte en una subclase de la clase de destino. No hay necesidad de interfaces.

Entonces, los proxies dinámicos de Java pueden proxy: public class Foo implements iFoodonde CGLIB puede proxy:public class Foo

EDITAR:

Debo mencionar que debido a que javassist y CGLIB usan proxy mediante subclases, esta es la razón por la que no puede declarar métodos finales o hacer que la clase sea final cuando se usan marcos que dependen de esto. Eso evitaría que estas bibliotecas permitan subclasificar su clase y anular sus métodos.

raphaëλ
fuente
Gracias..!! ¡Pero sería útil si pudiera darme un código de ejemplo (o Enlace) para ilustrar el uso de uno sobre otro en algún caso ... !!!
KDjava
1
Tenga en cuenta que los servidores proxy JDK en realidad están cediendo el proxy para IFoo, no para ningún tipo de Foo. Es una distinción bastante importante. Además, los proxies cglib son subclases completas, ¡aproveche eso! Use filtros para solo los métodos proxy que le interesan y use la clase generada directamente.
lscoughlin
9
También se debe tener en cuenta que la creación de subclase CGLib requiere saber lo suficiente sobre la superclase para poder llamar al constructor correcto con los argumentos correctos. A diferencia del proxy basado en interfaz que no se preocupa por los constructores. Esto hace que trabajar con proxies CGLib sea menos "automático" que los proxys JDK. Otra distinción está en el costo de "pila". Un proxy JDK siempre incurre en marcos de pila adicionales por llamada, mientras que un CGLib puede no costar ningún marco de pila adicional. Esto se vuelve cada vez más relevante cuanto más compleja se vuelve la aplicación (porque cuanto más grande es la pila, más hilos de memoria consumen).
Ray
1
cglib no puede representar los métodos finales, pero no arrojará una excepción gist.github.com/mhewedy/7345403cfa52e6f47563f8a204ec0e80
Muhammad Hewedy
Sí, CGLIB simplemente ignora los métodos finales.
yashjain12yj
56

Diferencias en funcionalidad

  • Los proxys JDK permiten implementar cualquier conjunto de interfaces durante la subclase Object. Cualquier método de interfaz, más Object::hashCode, Object::equalsy Object::toStringluego se reenvía a un InvocationHandler. Además, java.lang.reflect.Proxyse implementa la interfaz de biblioteca estándar .

  • cglib le permite implementar cualquier conjunto de interfaces mientras subclasifica cualquier clase no final. Además, los métodos se pueden anular opcionalmente, es decir, no todos los métodos no abstractos deben ser interceptados. Además, hay diferentes formas de implementar un método. También ofrece una InvocationHandlerclase (en un paquete diferente), pero también permite llamar a métodos súper utilizando interceptores más avanzados como, por ejemplo, a MethodInterceptor. Además, cglib puede mejorar el rendimiento mediante intercepciones especializadas como FixedValue. Una vez escribí un resumen de diferentes interceptores para cglib .

Diferencias de rendimiento

Proxies JDK se implementan ingenuamente con sólo un despachador de intercepción, la InvocationHandler. Esto requiere un envío de método virtual a una implementación que no siempre se puede incorporar. Cglib permite crear códigos de bytes especializados que a veces pueden mejorar el rendimiento. Aquí hay algunas comparaciones para implementar una interfaz con 18 métodos stub:

            cglib                   JDK proxy
creation    804.000     (1.899)     973.650     (1.624)
invocation    0.002     (0.000)       0.005     (0.000)

El tiempo se observa en nanosegundos con desviación estándar entre llaves. Puede encontrar más detalles sobre el punto de referencia en el tutorial de Byte Buddy, donde Byte Buddy es una alternativa más moderna a cglib. Además, tenga en cuenta que cglib ya no está en desarrollo activo.

Rafael Winterhalter
fuente
2
¿Por qué la documentación de primavera favorece el proxy JDK sobre cglib dados los beneficios de rendimiento de este último? docs.spring.io/spring/docs/2.5.x/reference/…
P4ndaman
2
Cglib es una dependencia externa y actualmente no es compatible. Confiar en un software de terceros siempre es una apuesta, por lo que es mejor cuando la menor cantidad de personas posible confía en él.
Rafael Winterhalter
En su blog dice: "Sin embargo, debe tener cuidado al llamar a un método en el objeto proxy que viene con el método de invocación InvocationHandler #. Todas las llamadas en este método se enviarán con el mismo InvocationHandler y, por lo tanto, podrían dar lugar a un bucle sin fin ". ¿Qué quieres decir?
Koray Tugay
Si llama a un método en el objeto proxy, cualquier llamada se enruta a través de nuestro controlador de invocación. Si alguna llamada del controlador de invocación delega una llamada al objeto, se produce la recursión mencionada.
Rafael Winterhalter
Hola Rafael, mensaje no relacionado con tu respuesta, te estoy haciendo un ping sobre una edición realizada hace 5 años . Como cglib aparentemente todavía tiene compromisos en 2019, y no muestra ningún desarrollo detenido en su archivo Léame, he eliminado su declaración del extracto de la etiqueta. Siéntase libre de mejorar la descripción / extracto de la etiqueta si hay algo relevante que mencionar.
Cœur
28

Proxy dinámico: implementaciones dinámicas de interfaces en tiempo de ejecución utilizando JDK Reflection API .

Ejemplo: Spring usa proxys dinámicos para transacciones de la siguiente manera:

ingrese la descripción de la imagen aquí

El proxy generado viene encima del bean. Agrega comportamiento transnacional al frijol. Aquí, el proxy se genera dinámicamente en tiempo de ejecución utilizando la API JDK Reflection.

Cuando se detiene una aplicación, el proxy se destruirá y solo tendremos interfaz y bean en el sistema de archivos.


En el ejemplo anterior tenemos interfaz. Pero en la mayoría de la implementación de la interfaz no es la mejor. Entonces bean no implementa una interfaz, en ese caso usamos herencia:

ingrese la descripción de la imagen aquí

Para generar tales proxies, Spring usa una biblioteca de terceros llamada CGLib .

CGLib ( C ode G eneration Lib rary ) está construido sobre ASM , esto se usa principalmente para generar proxy extendiendo bean y agrega comportamiento de bean en los métodos proxy.

Ejemplos de proxy dinámico JDK y CGLib

Ref primavera

Premraj
fuente
5

De la documentación de Spring :

Spring AOP usa proxys dinámicos JDK o CGLIB para crear el proxy para un objeto objetivo dado. (Se prefieren los proxys dinámicos JDK siempre que tenga una opción).

Si el objeto de destino que se va a implementar implementa al menos una interfaz, se utilizará un proxy dinámico JDK. Todas las interfaces implementadas por el tipo de destino serán proxy. Si el objeto de destino no implementa ninguna interfaz, se creará un proxy CGLIB.

Si desea forzar el uso de proxy CGLIB (por ejemplo, para proxy cada método definido para el objeto de destino, no solo los implementados por sus interfaces), puede hacerlo. Sin embargo, hay algunos problemas a considerar:

Los métodos finales no pueden ser aconsejados, ya que no pueden ser anulados.

Necesitará los binarios CGLIB 2 en su classpath, mientras que los proxies dinámicos están disponibles con el JDK. Spring le avisará automáticamente cuando necesite CGLIB y las clases de la biblioteca CGLIB no se encuentren en el classpath.

El constructor de su objeto proxy se llamará dos veces. Esta es una consecuencia natural del modelo proxy CGLIB por el cual se genera una subclase para cada objeto proxy. Para cada instancia proxy, se crean dos objetos: el objeto proxy real y una instancia de la subclase que implementa el consejo. Este comportamiento no se exhibe cuando se utilizan servidores proxy JDK. Por lo general, llamar al constructor del tipo proxy dos veces no es un problema, ya que generalmente solo se realizan asignaciones y no se implementa una lógica real en el constructor.

Taras Melnyk
fuente