Was ist der Unterschied zwischen dem dynamischen JDK-Proxy und CGLib?

147

Was ist im Fall des Proxy-Entwurfsmusters der Unterschied zwischen dem dynamischen Proxy von JDK und den APIs zur dynamischen Codegenerierung von Drittanbietern wie CGLib ?

Was ist der Unterschied zwischen der Verwendung beider Ansätze und wann sollte man einander vorziehen?

KDjava
quelle
3
Den Code erhalten Sie hier: < gist.github.com/ksauzz/1563486 >. In cglib können Sie sowohl Klassenproxy als auch Schnittstellenproxy erstellen. Spring verwendet standardmäßig CGlib, während AspectJ Java-Proxy verwendet. Lesen Sie dies auch: jnb.ociweb.com/jnb/jnbNov2005.html ;)
Subhadeep Ray

Antworten:

185

JDK Dynamic Proxy kann nur Proxy über Schnittstelle (daher muss Ihre Zielklasse eine Schnittstelle implementieren, die dann auch von der Proxy-Klasse implementiert wird).

CGLIB (und Javassist) können durch Unterklassen einen Proxy erstellen. In diesem Szenario wird der Proxy zu einer Unterklasse der Zielklasse. Keine Notwendigkeit für Schnittstellen.

Java Dynamic-Proxys können also Proxys public class Foo implements iFooerstellen : wobei CGLIB Proxys verwenden kann:public class Foo

BEARBEITEN:

Ich sollte erwähnen, dass, weil Javassist und CGLIB Proxy durch Unterklassen verwenden, dies der Grund ist, warum Sie keine endgültigen Methoden deklarieren oder die Klasse endgültig machen können, wenn Sie Frameworks verwenden, die darauf basieren. Dies würde diese Bibliotheken daran hindern, Ihre Klasse zu unterklassifizieren und Ihre Methoden zu überschreiben.

raphaëλ
quelle
Vielen Dank..!! aber es wäre hilfreich, wenn Sie mir einen Beispielcode (oder Link) geben könnten, um die Verwendung in einem anderen Fall gegenüber einem anderen zu veranschaulichen. !!!
KDjava
1
Beachten Sie, dass JDK-Proxys den Proxy für IFoo tatsächlich nicht für irgendeine Art von Foo verwenden. Es ist eine ziemlich wichtige Unterscheidung. Auch cglib-Proxys sind vollständige Unterklassen - nutzen Sie das! Verwenden Sie Filter nur für Proxy-Methoden, die Ihnen wichtig sind, und verwenden Sie die generierte Klasse direkt.
lscoughlin
9
Es sollte auch beachtet werden, dass für die Erstellung von CGLib-Unterklassen genügend Kenntnisse über die Superklasse erforderlich sind, um den richtigen Konstruktor mit den richtigen Argumenten aufrufen zu können. Im Gegensatz zu schnittstellenbasierten Proxys, die sich nicht um Konstruktoren kümmern. Dies macht die Arbeit mit CGLib-Proxys weniger "automatisch" als mit JDK-Proxys. Ein weiterer Unterschied liegt in den "Stapel" -Kosten. Ein JDK-Proxy verursacht immer zusätzliche Stapelrahmen pro Aufruf, während eine CGLib möglicherweise keine zusätzlichen Stapelrahmen kostet. Dies wird immer relevanter, je komplexer die App wird (denn je größer der Stapel, desto mehr Speicher-Threads verbrauchen).
Ray
1
cglib kann keine endgültigen Methoden vertreten, wird jedoch keine Ausnahme auslösen. gist.github.com/mhewedy/7345403cfa52e6f47563f8a204ec0e80
Muhammad Hewedy
Ja, CGLIB ignoriert einfach die endgültigen Methoden.
Yashjain12yj
56

Unterschiede in der Funktionalität

  • Die JDK-Proxys ermöglichen die Implementierung eines beliebigen Satzes von Schnittstellen während der Unterklasse Object. Jede Schnittstellenmethode, und Object::hashCode, Object::equalsund Object::toStringwird dann an einen weitergeleitet InvocationHandler. Zusätzlich ist die Standardbibliotheksschnittstelle java.lang.reflect.Proxyimplementiert.

  • Mit cglib können Sie einen beliebigen Satz von Schnittstellen implementieren, während Sie eine nicht endgültige Klasse unterordnen. Außerdem können Methoden optional überschrieben werden, dh nicht alle nicht abstrakten Methoden müssen abgefangen werden. Darüber hinaus gibt es verschiedene Möglichkeiten, eine Methode zu implementieren. Es bietet auch eine InvocationHandlerKlasse (in einem anderen Paket), aber es ermöglicht auch das Aufrufen von Supermethoden, indem fortgeschrittenere Interceptors verwendet werden, wie zum Beispiel a MethodInterceptor. Darüber hinaus kann cglib die Leistung durch spezielle Interceptions wie verbessern FixedValue. Ich habe einmal eine Zusammenfassung verschiedener Interceptors für cglib geschrieben .

Leistungsunterschiede

JDK-Proxys werden eher naiv mit nur einem Interception Dispatcher implementiert, dem InvocationHandler. Dies erfordert einen virtuellen Methodenversand an eine Implementierung, die nicht immer inline sein kann. Mit Cglib können Sie speziellen Bytecode erstellen, der manchmal die Leistung verbessern kann. Hier einige Vergleiche zur Implementierung einer Schnittstelle mit 18 Stub-Methoden:

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

Die Zeit wird in Nanosekunden mit Standardabweichung in geschweiften Klammern angegeben. Weitere Informationen zum Benchmark finden Sie im Tutorial von Byte Buddy, in dem Byte Buddy eine modernere Alternative zu cglib darstellt. Beachten Sie auch, dass sich cglib nicht mehr in der aktiven Entwicklung befindet.

Rafael Winterhalter
quelle
2
Warum bevorzugt die Frühjahrsdokumentation JDK-Proxy gegenüber cglib angesichts der Leistungsvorteile von letzterem? docs.spring.io/spring/docs/2.5.x/reference/…
P4ndaman
2
Cglib ist eine externe Abhängigkeit und wird derzeit nicht unterstützt. Sich auf Software von Drittanbietern zu verlassen, ist immer ein Glücksspiel. Daher ist es am besten, wenn sich so wenige Menschen wie möglich darauf verlassen.
Rafael Winterhalter
In Ihrem Blog sagen Sie: "Sie sollten jedoch vorsichtig sein, wenn Sie eine Methode für das Proxy-Objekt aufrufen, das mit der InvocationHandler # invoke-Methode geliefert wird. Alle Aufrufe dieser Methode werden mit demselben InvocationHandler ausgelöst und führen daher möglicherweise zu einer Endlosschleife . " Was meinst du?
Koray Tugay
Wenn Sie eine Methode für das Proxy-Objekt aufrufen, wird jeder Aufruf über unseren Aufrufhandler weitergeleitet. Wenn ein Aufruf des Aufrufhandlers einen Aufruf an das Objekt delegiert, erfolgt die erwähnte Rekursion.
Rafael Winterhalter
Hallo Rafael, Nachricht, die nichts mit deiner Antwort zu tun hat. Ich pinge dich wegen einer vor 5 Jahren vorgenommenen Bearbeitung an . Da cglib 2019 offenbar noch Commits hat und in seiner Readme- Datei keine festgefahrene Entwicklung anzeigt, habe ich Ihre Aussage aus dem Tag-Auszug entfernt. Fühlen Sie sich frei, die Tag-Beschreibung / den Auszug zu verbessern, wenn etwas Relevantes zu erwähnen ist.
Cœur
28

Dynamischer Proxy: Dynamische Implementierungen von Schnittstellen zur Laufzeit mithilfe der JDK Reflection API .

Beispiel: Spring verwendet dynamische Proxys für Transaktionen wie folgt:

Geben Sie hier die Bildbeschreibung ein

Der generierte Proxy kommt über Bean. Es fügt der Bean transnationales Verhalten hinzu. Hier wird der Proxy zur Laufzeit mithilfe der JDK Reflection API dynamisch generiert.

Wenn eine Anwendung gestoppt wird, wird der Proxy zerstört und wir haben nur Schnittstelle und Bean im Dateisystem.


Im obigen Beispiel haben wir eine Schnittstelle. Aber in den meisten Fällen ist die Implementierung der Schnittstelle nicht die beste. Bean implementiert also keine Schnittstelle. In diesem Fall verwenden wir die Vererbung:

Geben Sie hier die Bildbeschreibung ein

Um solche Proxys zu generieren, verwendet Spring eine Drittanbieter-Bibliothek namens CGLib .

Cglib ( C ode G eneration Lib rary) oben auf gebaut ist ASM , dies in erster Linie verwendet , um den Proxy erstreckende Bohne erzeugt und fügt bean Verhalten in dem Proxy - Verfahren.

Beispiele für JDK Dynamic Proxy und CGLib

Feder ref

Premraj
quelle
5

Aus der Spring-Dokumentation :

Spring AOP verwendet entweder dynamische JDK-Proxys oder CGLIB, um den Proxy für ein bestimmtes Zielobjekt zu erstellen. (Dynamische JDK-Proxys werden bevorzugt, wenn Sie die Wahl haben).

Wenn das zu proxyende Zielobjekt mindestens eine Schnittstelle implementiert, wird ein dynamischer JDK-Proxy verwendet. Alle vom Zieltyp implementierten Schnittstellen werden als Proxy bereitgestellt. Wenn das Zielobjekt keine Schnittstellen implementiert, wird ein CGLIB-Proxy erstellt.

Wenn Sie die Verwendung von CGLIB-Proxy erzwingen möchten (z. B. um jede für das Zielobjekt definierte Methode zu vertreten, nicht nur die von seinen Schnittstellen implementierten), können Sie dies tun. Es sind jedoch einige Punkte zu beachten:

endgültige Methoden können nicht empfohlen werden, da sie nicht überschrieben werden können.

Sie benötigen die CGLIB 2-Binärdateien in Ihrem Klassenpfad, während dynamische Proxys mit dem JDK verfügbar sind. Spring warnt Sie automatisch, wenn CGLIB benötigt wird und die CGLIB-Bibliotheksklassen nicht im Klassenpfad gefunden werden.

Der Konstruktor Ihres Proxy-Objekts wird zweimal aufgerufen. Dies ist eine natürliche Folge des CGLIB-Proxy-Modells, bei dem für jedes Proxy-Objekt eine Unterklasse generiert wird. Für jede Proxy-Instanz werden zwei Objekte erstellt: das eigentliche Proxy-Objekt und eine Instanz der Unterklasse, die den Hinweis implementiert. Dieses Verhalten wird bei Verwendung von JDK-Proxys nicht angezeigt. Normalerweise ist das zweimalige Aufrufen des Konstruktors des Proxy-Typs kein Problem, da normalerweise nur Zuweisungen stattfinden und keine echte Logik im Konstruktor implementiert ist.

Taras Melnyk
quelle