Jaka jest różnica między dynamicznym proxy JDK a CGLib?

147

W przypadku wzorca projektowego proxy , jaka jest różnica między dynamicznym proxy JDK a interfejsami API do generowania kodu dynamicznego innych firm, takimi jak CGLib ?

Jaka jest różnica między stosowaniem obu podejść i kiedy należy je preferować?

KDjava
źródło
3
Pobierz kod tutaj: < gist.github.com/ksauzz/1563486 >. W cglib można utworzyć zarówno proxy klasy, jak i proxy interfejsu. Spring domyślnie używa CGlib, podczas gdy AspectJ używa proxy Java. Przeczytaj to również: jnb.ociweb.com/jnb/jnbNov2005.html ;)
Subhadeep Ray,

Odpowiedzi:

185

JDK Dynamiczne proxy może proxy tylko przez interfejs (więc Twoja klasa docelowa musi zaimplementować interfejs, który jest następnie implementowany przez klasę proxy).

CGLIB (i javassist) mogą tworzyć proxy przez tworzenie podklas. W tym scenariuszu proxy staje się podklasą klasy docelowej. Nie potrzeba interfejsów.

Więc dynamiczne proxy Java mogą proxy: public class Foo implements iFoogdzie CGLIB może proxy:public class Foo

EDYTOWAĆ:

Powinienem o tym wspomnieć, ponieważ javassist i CGLIB używają proxy przez podklasy, że jest to powód, dla którego nie można zadeklarować ostatecznych metod ani uczynić klasy ostateczną, gdy używa się frameworków, które na tym polegają. To powstrzymałoby te biblioteki przed zezwalaniem na podklasy twojej klasy i nadpisywanie twoich metod.

raphaëλ
źródło
dzięki..!! ale byłoby pomocne, gdybyś mógł mi podać jeden przykładowy kod (lub Link), aby w niektórych przypadkach zilustrować jego użycie w porównaniu z innym .. !!!
KDjava
1
Zauważ, że proxy JDK w rzeczywistości rezygnują z proxy dla IFoo, a nie dla żadnego Foo dla żadnego rodzaju. To dość ważna różnica. Ponadto serwery proxy cglib są pełnymi podklasami - skorzystaj z tego! Użyj filtrów, aby użyć tylko tych metod proxy, na których Ci zależy, i użyj bezpośrednio wygenerowanej klasy.
lscoughlin
9
Należy również zauważyć, że tworzenie podklasy CGLib wymaga wystarczającej wiedzy o superklasie, aby móc wywołać poprawny konstruktor z odpowiednimi argumentami. W przeciwieństwie do proxy opartego na interfejsie, który nie przejmuje się konstruktorami. To sprawia, że ​​praca z proxy CGLib jest mniej „automatyczna” niż z proxy JDK. Kolejną różnicą jest koszt „stosu”. Proxy JDK zawsze generuje dodatkowe ramki stosu na wywołanie, podczas gdy CGLib może nie kosztować żadnych dodatkowych ramek stosu. Staje się to coraz bardziej istotne, im bardziej złożona jest aplikacja (ponieważ im większy stos, tym więcej wątków pamięci zużywa).
Ray,
1
cglib nie może proxy ostatecznych metod, ale nie zgłosi
Muhammad Hewedy
Tak, CGLIB po prostu ignoruje ostateczne metody.
yashjain12yj
56

Różnice w funkcjonalności

  • Serwery proxy JDK pozwalają na implementację dowolnego zestawu interfejsów podczas tworzenia podklas Object. Dowolna metoda interfejsu plus Object::hashCode, Object::equalsa Object::toStringnastępnie jest przekazywana do pliku InvocationHandler. Dodatkowo java.lang.reflect.Proxyzaimplementowano standardowy interfejs biblioteki .

  • cglib umożliwia zaimplementowanie dowolnego zestawu interfejsów podczas tworzenia podklas dowolnej klasy, która nie jest ostateczna. Ponadto metody można opcjonalnie przesłonić, tj. Nie wszystkie metody nieabstrakcyjne muszą zostać przechwycone. Ponadto istnieją różne sposoby implementacji metody. Oferuje również InvocationHandlerklasę (w innym pakiecie), ale pozwala również wywoływać super metody przy użyciu bardziej zaawansowanych przechwytywaczy, na przykład a MethodInterceptor. Ponadto cglib może poprawić wydajność dzięki wyspecjalizowanym przechwyceniom, takim jak FixedValue. Kiedyś napisałem podsumowanie różnych przechwytywaczy dla cglib .

Różnice w wydajności

Serwery proxy JDK są implementowane raczej naiwnie z tylko jednym dyspozytorem przechwytywania, plikiem InvocationHandler. Wymaga to wysłania metody wirtualnej do implementacji, która nie zawsze może być wbudowana. Cglib pozwala na tworzenie wyspecjalizowanego kodu bajtowego, co czasami może poprawić wydajność. Oto kilka porównań implementacji interfejsu z 18 metodami pośredniczącymi:

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

Czas jest podawany w nanosekundach z odchyleniem standardowym w szelkach. Więcej szczegółów na temat testu porównawczego można znaleźć w samouczku Byte Buddy, w którym Byte Buddy jest bardziej nowoczesną alternatywą dla cglib. Należy również zauważyć, że cglib nie jest już aktywnie rozwijany.

Rafael Winterhalter
źródło
2
Dlaczego dokumentacja wiosenna faworyzuje proxy JDK zamiast cglib, biorąc pod uwagę zalety wydajności tego drugiego? docs.spring.io/spring/docs/2.5.x/reference/…
P4ndaman
2
Cglib jest zewnętrzną zależnością i obecnie nie jest obsługiwana. Poleganie na oprogramowaniu innych firm jest zawsze ryzykowne, więc najlepiej jest, gdy polega na nim jak najmniej osób.
Rafael Winterhalter
W swoim blogu mówisz: „Należy jednak zachować ostrożność podczas wywoływania metody w obiekcie proxy, który jest dostarczany z metodą InvocationHandler # invoke. Wszystkie wywołania tej metody będą wysyłane za pomocą tego samego obiektu InvocationHandler i dlatego mogą powodować niekończącą się pętlę ”. Co masz na myśli?
Koray Tugay
Jeśli wywołasz metodę na obiekcie proxy, każde wywołanie jest kierowane przez nasz program obsługi wywołań. Jeśli jakikolwiek program obsługi wywołania deleguje wywołanie obiektu, następuje wspomniana rekursja.
Rafael Winterhalter
Cześć Rafael, wiadomość niezwiązana z Twoją odpowiedzią, Pinguję o zmianę dokonaną 5 lat temu . Ponieważ cglib najwyraźniej nadal ma zatwierdzenia w 2019 r. I nie pokazuje żadnego zatrzymanego rozwoju w swoim pliku readme, usunąłem twoje oświadczenie z fragmentu tagu. Jeśli jest coś, o czym warto wspomnieć, możesz poprawić opis / fragment tagu.
Cœur,
28

Dynamiczne proxy: dynamiczne implementacje interfejsów w czasie wykonywania przy użyciu JDK Reflection API .

Przykład: Spring używa dynamicznych proxy do transakcji w następujący sposób:

wprowadź opis obrazu tutaj

Wygenerowany proxy znajduje się na szczycie fasoli. Dodaje fasoli do międzynarodowego zachowania. Tutaj proxy generuje się dynamicznie w czasie wykonywania przy użyciu JDK Reflection API.

Kiedy aplikacja zostanie zatrzymana, proxy zostanie zniszczone, a my będziemy mieć tylko interfejs i bean w systemie plików.


W powyższym przykładzie mamy interfejs. Jednak w większości implementacji interfejs nie jest najlepszy. Więc bean nie implementuje interfejsu, w takim przypadku używamy dziedziczenia:

wprowadź opis obrazu tutaj

Aby wygenerować takie proxy, Spring używa biblioteki innej firmy o nazwie CGLib .

CGLib ( C ode G eneration Lib rary ) jest zbudowany na bazie ASM , jest używany głównie do generowania komponentu bean rozszerzającego proxy i dodaje zachowanie fasoli w metodach proxy.

Przykłady dla JDK Dynamic proxy i CGLib

Wiosna ref

Premraj
źródło
5

Z dokumentacji Spring :

Spring AOP używa dynamicznych proxy JDK lub CGLIB do tworzenia proxy dla danego obiektu docelowego. (Dynamiczne proxy JDK są preferowane, gdy masz wybór).

Jeśli obiekt docelowy, który ma być proxy, implementuje co najmniej jeden interfejs, zostanie użyty dynamiczny serwer proxy JDK. Wszystkie interfejsy zaimplementowane przez typ docelowy będą proxy. Jeśli obiekt docelowy nie implementuje żadnych interfejsów, zostanie utworzony serwer proxy CGLIB.

Jeśli chcesz wymusić użycie proxy CGLIB (na przykład do proxy każdej metody zdefiniowanej dla obiektu docelowego, a nie tylko tych zaimplementowanych przez jego interfejsy), możesz to zrobić. Należy jednak wziąć pod uwagę kilka kwestii:

nie można zalecić ostatecznych metod, ponieważ nie można ich zastąpić.

Będziesz potrzebował plików binarnych CGLIB 2 w swojej ścieżce klas, podczas gdy dynamiczne proxy są dostępne z JDK. Spring automatycznie ostrzeże Cię, gdy potrzebuje CGLIB, a klasy biblioteki CGLIB nie zostaną znalezione w ścieżce klas.

Konstruktor twojego obiektu proxy zostanie wywołany dwukrotnie. Jest to naturalna konsekwencja modelu proxy CGLIB, w którym podklasa jest generowana dla każdego obiektu proxy. Dla każdej instancji proxy tworzone są dwa obiekty: rzeczywisty obiekt proxy i instancja podklasy, która implementuje poradę. To zachowanie nie jest widoczne podczas korzystania z serwerów proxy JDK. Zwykle dwukrotne wywołanie konstruktora typu proxy nie jest problemem, ponieważ zwykle mają miejsce tylko przypisania i nie ma rzeczywistej logiki w konstruktorze.

Taras Melnyk
źródło