Quelle est la différence entre le proxy dynamique JDK et CGLib?

147

Dans le cas du modèle de conception de proxy , quelle est la différence entre le proxy dynamique de JDK et les API de génération de code dynamique tierces telles que CGLib ?

Quelle est la différence entre l'utilisation des deux approches et quand doit-on préférer l'une à l'autre?

KDjava
la source
3
Obtenez le code ici: < gist.github.com/ksauzz/1563486 >. Dans cglib, vous pouvez créer à la fois un proxy de classe et un proxy d'interface. Spring utilise CGlib par défaut tandis qu'AspectJ utilise le proxy Java. Lisez également ceci: jnb.ociweb.com/jnb/jnbNov2005.html ;)
Subhadeep Ray

Réponses:

185

Le proxy dynamique JDK ne peut utiliser le proxy que par interface (votre classe cible doit donc implémenter une interface, qui est ensuite également implémentée par la classe proxy).

CGLIB (et javassist) peuvent créer un proxy par sous-classement. Dans ce scénario, le proxy devient une sous-classe de la classe cible. Pas besoin d'interfaces.

Ainsi, les proxys Java Dynamic peuvent proxy: public class Foo implements iFoolà où CGLIB peut proxy:public class Foo

ÉDITER:

Je dois mentionner que parce que javassist et CGLIB utilisent le proxy en sous-classant, c'est la raison pour laquelle vous ne pouvez pas déclarer de méthodes finales ou rendre la classe finale lorsque vous utilisez des frameworks qui reposent sur cela. Cela empêcherait ces bibliothèques d'autoriser la sous-classe de votre classe et de remplacer vos méthodes.

raphaëλ
la source
Merci..!! mais ce serait utile si vous pouviez me donner un exemple de code (ou lien) pour illustrer son utilisation par rapport à un autre dans certains cas .. !!!
KDjava
1
Notez que les proxys JDK ne cèdent en fait pas le proxy pour IFoo pour aucun type de Foo. C'est une distinction assez importante. De plus, les proxies cglib sont des sous-classes complètes - profitez-en! Utilisez des filtres uniquement pour les méthodes de proxy qui vous intéressent et utilisez directement la classe générée.
lscoughlin
9
Il convient également de noter que la création de sous-classes CGLib nécessite une connaissance suffisante de la super classe pour pouvoir appeler le constructeur correct avec les bons arguments. Contrairement au proxy basé sur l'interface qui ne se soucie pas des constructeurs. Cela rend le travail avec les proxys CGLib moins "automatique" qu'avec les proxys JDK. Une autre distinction réside dans le coût «de pile». Un proxy JDK entraîne toujours des trames de pile supplémentaires par appel tandis qu'un CGLib peut ne pas coûter de trames de pile supplémentaires. Cela devient de plus en plus pertinent à mesure que l'application devient complexe (car plus la pile est grande, plus les threads de mémoire consomment).
Ray
1
cglib ne peut pas utiliser de proxy pour les méthodes finales, mais ne lèvera
Muhammad Hewedy
Oui, CGLIB ignore simplement les méthodes finales.
yashjain12yj
56

Différences de fonctionnalité

  • Les proxies JDK permettent d'implémenter n'importe quel ensemble d'interfaces lors du sous-classement Object. Toute méthode d'interface, plus Object::hashCode, Object::equalset Object::toStringest ensuite transmise à un fichier InvocationHandler. En outre, l'interface de bibliothèque standard java.lang.reflect.Proxyest implémentée.

  • cglib vous permet d'implémenter n'importe quel ensemble d'interfaces tout en sous-classant n'importe quelle classe non finale. De plus, les méthodes peuvent être écrasées facultativement, c'est-à-dire que toutes les méthodes non abstraites n'ont pas besoin d'être interceptées. De plus, il existe différentes manières de mettre en œuvre une méthode. Il propose également une InvocationHandlerclasse (dans un package différent), mais il permet également d'appeler des super méthodes en utilisant des intercepteurs plus avancés comme par exemple a MethodInterceptor. De plus, cglib peut améliorer les performances grâce à des interceptions spécialisées comme FixedValue. J'ai écrit une fois un résumé des différents intercepteurs pour cglib .

Différences de performances

Les proxies JDK sont implémentés assez naïvement avec un seul répartiteur d'interception, le InvocationHandler. Cela nécessite l'envoi d'une méthode virtuelle à une implémentation qui ne peut pas toujours être intégrée. Cglib permet de créer du byte code spécialisé ce qui peut parfois améliorer les performances. Voici quelques comparaisons pour implémenter une interface avec 18 méthodes de stub:

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

Le temps est noté en nanosecondes avec un écart type entre accolades. Vous pouvez trouver plus de détails sur le benchmark dans le tutoriel de Byte Buddy, où Byte Buddy est une alternative plus moderne à cglib. Notez également que cglib n'est plus en développement actif.

Rafael Winterhalter
la source
2
Pourquoi la documentation Spring favorise-t-elle le proxy JDK par rapport à cglib étant donné les avantages de performance de ce dernier? docs.spring.io/spring/docs/2.5.x/reference/…
P4ndaman
2
Cglib est une dépendance externe et n'est actuellement pas prise en charge. S'appuyer sur des logiciels tiers est toujours un pari, il est donc préférable que le moins de personnes possible en dépendent.
Rafael Winterhalter
Dans votre blog, vous dites: "Cependant, vous devez faire attention lorsque vous appelez une méthode sur l'objet proxy fourni avec la méthode InvocationHandler # invoke. Tous les appels sur cette méthode seront distribués avec le même InvocationHandler et peuvent donc entraîner une boucle sans fin . " Que voulez-vous dire?
Koray Tugay
Si vous appelez une méthode sur l'objet proxy, tout appel est acheminé via notre gestionnaire d'appels. Si un appel de gestionnaire d'appel délègue un appel à l'objet, la récursivité mentionnée se produit.
Rafael Winterhalter
Salut Rafael, message sans rapport avec votre réponse, je vous demande une modification faite il y a 5 ans . Comme cglib a apparemment toujours des commits en 2019 et ne montre aucun développement arrêté dans son readme, j'ai supprimé votre déclaration de l'extrait de balise. N'hésitez pas à améliorer la description / l'extrait de la balise s'il y a quelque chose de pertinent à mentionner.
Cœur
28

Proxy dynamique: implémentations dynamiques d'interfaces au moment de l'exécution à l'aide de l' API JDK Reflection .

Exemple: Spring utilise des proxys dynamiques pour les transactions comme suit:

entrez la description de l'image ici

Le proxy généré vient au-dessus du bean. Cela ajoute un comportement transnational au haricot. Ici, le proxy génère dynamiquement au moment de l'exécution à l'aide de l'API JDK Reflection.

Lorsqu'une application est arrêtée, le proxy sera détruit et nous n'aurons que l'interface et le bean sur le système de fichiers.


Dans l'exemple ci-dessus, nous avons l'interface. Mais dans la plupart des implémentations, l'interface n'est pas la meilleure. Donc bean n'implémente pas d'interface, dans ce cas nous utilisons l'héritage:

entrez la description de l'image ici

Afin de générer de tels proxys, Spring utilise une bibliothèque tierce appelée CGLib .

CGLib ( C ode G eneration Lib rary ) est construit sur ASM , il est principalement utilisé pour générer le bean d'extension de proxy et ajoute un comportement de bean dans les méthodes de proxy.

Exemples pour le proxy dynamique JDK et CGLib

Réf printemps

Premraj
la source
5

De la documentation Spring :

Spring AOP utilise des proxys dynamiques JDK ou CGLIB pour créer le proxy pour un objet cible donné. (Les proxys dynamiques JDK sont préférés chaque fois que vous avez le choix).

Si l'objet cible à proxy implémente au moins une interface, un proxy dynamique JDK sera utilisé. Toutes les interfaces implémentées par le type de cible seront mandatées. Si l'objet cible n'implémente aucune interface, un proxy CGLIB sera créé.

Si vous voulez forcer l'utilisation du proxy CGLIB (par exemple, pour proxy chaque méthode définie pour l'objet cible, pas seulement celles implémentées par ses interfaces), vous pouvez le faire. Cependant, il y a quelques problèmes à considérer:

les méthodes finales ne peuvent pas être conseillées, car elles ne peuvent pas être remplacées.

Vous aurez besoin des binaires CGLIB 2 sur votre chemin de classe, tandis que les proxys dynamiques sont disponibles avec le JDK. Spring vous avertira automatiquement lorsqu'il a besoin de CGLIB et que les classes de la bibliothèque CGLIB ne sont pas trouvées sur le chemin de classe.

Le constructeur de votre objet mandaté sera appelé deux fois. C'est une conséquence naturelle du modèle proxy CGLIB dans lequel une sous-classe est générée pour chaque objet proxy. Pour chaque instance mandatée, deux objets sont créés: l'objet mandaté réel et une instance de la sous-classe qui implémente l'avis. Ce comportement n'apparaît pas lors de l'utilisation de proxys JDK. Habituellement, appeler le constructeur du type proxy deux fois n'est pas un problème, car il n'y a généralement que des affectations en cours et aucune logique réelle n'est implémentée dans le constructeur.

Taras Melnyk
la source