Apa perbedaan antara proxy dinamis JDK dan CGLib?

147

Dalam hal Pola Desain Proxy , Apa perbedaan antara Dynamic Proxy JDK dan API pembuatan kode dinamis pihak ketiga seperti CGLib ?

Apa perbedaan antara menggunakan kedua pendekatan tersebut dan kapan satu harus memilih satu dari yang lain?

KDjava
sumber
3
Dapatkan kode di sini: < gist.github.com/ksauzz/1563486 >. Di cglib Anda dapat membuat proksi kelas dan proksi antarmuka. Spring menggunakan CGlib secara default sedangkan AspectJ menggunakan proxy Java. Baca ini juga: jnb.ociweb.com/jnb/jnbNov2005.html ;)
Subhadeep Ray

Jawaban:

185

JDK Dynamic proxy hanya dapat proxy dengan antarmuka (sehingga kelas target Anda perlu mengimplementasikan antarmuka, yang kemudian juga diimplementasikan oleh kelas proxy).

CGLIB (dan javassist) dapat membuat proxy dengan mensubclassing. Dalam skenario ini proksi menjadi subkelas dari kelas target. Tidak perlu antarmuka.

Jadi proksi Java Dynamic dapat proksi: public class Foo implements iFootempat CGLIB dapat proksi:public class Foo

EDIT:

Saya harus menyebutkan bahwa karena javassist dan CGLIB menggunakan proxy dengan mensubclassing, bahwa ini adalah alasan Anda tidak dapat mendeklarasikan metode final atau membuat kelas final saat menggunakan kerangka kerja yang mengandalkan ini. Itu akan menghentikan perpustakaan ini dari memungkinkan untuk subkelas kelas Anda dan menimpa metode Anda.

raphaëλ
sumber
Terima kasih..!! tetapi akan sangat membantu jika Anda dapat memberi saya satu contoh kode (atau Tautan) untuk menggambarkan penggunaan seseorang atas yang lain dalam beberapa kasus .. !!!
KDjava
1
Perhatikan bahwa proksi JDK benar-benar menghentikan proxy untuk IFoo bukan untuk segala jenis Foo sama sekali. Ini perbedaan yang agak penting. Juga, proksi cglib adalah subkelas penuh - manfaatkan itu! Gunakan filter hanya untuk metode proksi yang Anda pedulikan dan gunakan kelas yang dihasilkan secara langsung.
lscoughlin
9
Juga harus dicatat bahwa pembuatan kelas CGLib membutuhkan cukup pengetahuan tentang kelas super untuk dapat memanggil konstruktor yang benar dengan argumen yang tepat. Tidak seperti proxy berbasis antarmuka yang tidak peduli dengan konstruktor. Ini membuat bekerja dengan proksi CGLib kurang "otomatis" dari proksi JDK. Perbedaan lain adalah dalam biaya "tumpukan". Proxy JDK selalu mengeluarkan frame stack tambahan per panggilan sementara CGLib mungkin tidak dikenakan biaya frame stack tambahan. Ini menjadi semakin relevan semakin kompleks aplikasi (karena semakin besar tumpukan, semakin banyak memori yang digunakan).
Ray
1
cglib tidak dapat membuat proksi metode final, tetapi tidak akan melempar pengecualian gist.github.com/mhewedy/7345403cfa52e6f47563f8a204ec0e80
Muhammad Hewedy
Ya, CGLIB mengabaikan metode final.
yashjain12yj
56

Perbedaan fungsionalitas

  • Proxy JDK memungkinkan untuk mengimplementasikan set antarmuka apa pun saat subkelas Object. Metode antarmuka apa pun, plus Object::hashCode, Object::equalsdan Object::toStringkemudian diteruskan ke InvocationHandler. Selain itu, antarmuka perpustakaan standar java.lang.reflect.Proxyditerapkan.

  • cglib memungkinkan Anda untuk mengimplementasikan set antarmuka apa pun saat subkelas kelas non-final. Juga, metode dapat diganti secara opsional, yaitu tidak semua metode non-abstrak perlu dicegat. Selain itu, ada berbagai cara menerapkan metode. Ia juga menawarkan InvocationHandlerkelas (dalam paket yang berbeda), tetapi juga memungkinkan untuk memanggil metode super dengan menggunakan pencegat yang lebih canggih seperti misalnya a MethodInterceptor. Selain itu, cglib dapat meningkatkan kinerja dengan intersepsi khusus seperti FixedValue. Saya pernah menulis ringkasan pencegat berbeda untuk cglib .

Perbedaan kinerja

Proxy JDK diimplementasikan agak naif dengan hanya satu operator intersepsi, yang InvocationHandler. Ini membutuhkan pengiriman metode virtual ke implementasi yang tidak selalu dapat disatukan. Cglib memungkinkan untuk membuat kode byte khusus yang terkadang dapat meningkatkan kinerja. Berikut adalah beberapa perbandingan untuk mengimplementasikan antarmuka dengan metode 18 rintisan:

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

Waktu dicatat dalam nanodetik dengan standar deviasi dalam kawat gigi. Anda dapat menemukan rincian lebih lanjut tentang tolok ukur dalam tutorial Byte Buddy, di mana Byte Buddy adalah alternatif yang lebih modern untuk cglib. Juga, perhatikan bahwa cglib tidak lagi dalam pengembangan aktif.

Rafael Winterhalter
sumber
2
Mengapa dokumentasi pegas lebih menyukai proksi JDK daripada cglib mengingat manfaat kinerja yang terakhir? docs.spring.io/spring/docs/2.5.x/reference/…
P4ndaman
2
Cglib adalah ketergantungan eksternal dan saat ini tidak didukung. Mengandalkan perangkat lunak pihak ketiga selalu merupakan pertaruhan, jadi yang terbaik adalah ketika sesedikit mungkin orang mengandalkannya.
Rafael Winterhalter
Di blog Anda, Anda berkata: "Namun, Anda harus berhati-hati ketika memanggil metode pada objek proxy yang datang dengan metode invokasiHandler # invoke. Semua panggilan pada metode ini akan dikirim dengan InvocationHandler yang sama dan karenanya dapat mengakibatkan loop tak berujung . " Maksud kamu apa?
Koray Tugay
Jika Anda memanggil metode pada objek proxy, panggilan apa pun dialihkan melalui, penangan doa kami. Jika ada penangan panggilan panggilan delegasi ke panggilan ke objek, rekursi yang disebutkan terjadi.
Rafael Winterhalter
Hai Rafael, pesan yang tidak terkait dengan jawaban Anda, saya akan mengirimi Anda suntingan yang dibuat 5 tahun yang lalu . Karena cglib tampaknya masih memiliki komitmen pada tahun 2019, dan tidak menunjukkan pengembangan apa pun yang ditahan di readme, saya telah menghapus pernyataan Anda dari kutipan tag. Jangan ragu untuk meningkatkan deskripsi tag / kutipan jika ada yang relevan untuk disebutkan.
Cœur
28

Dynamic proxy: Implementasi dinamis dari antarmuka saat runtime menggunakan JDK Reflection API .

Contoh: Spring menggunakan proksi dinamis untuk transaksi sebagai berikut:

masukkan deskripsi gambar di sini

Proxy yang dihasilkan berada di atas kacang. Ini menambahkan perilaku transnasional pada kacang. Di sini proksi menghasilkan secara dinamis saat runtime menggunakan JDK Reflection API.

Ketika aplikasi dihentikan, proksi akan dihancurkan dan kami hanya akan memiliki antarmuka dan kacang pada sistem file.


Pada contoh di atas kita memiliki antarmuka. Tetapi dalam sebagian besar implementasi antarmuka tidak terbaik. Jadi bean tidak mengimplementasikan antarmuka, dalam hal ini kami menggunakan warisan:

masukkan deskripsi gambar di sini

Untuk menghasilkan proxy seperti itu, Spring menggunakan perpustakaan pihak ketiga yang disebut CGLib .

CGLib ( C ode G eneration Lib rary) dibangun di atas ASM , ini terutama digunakan menghasilkan proxy yang memperpanjang kacang dan menambahkan perilaku kacang dalam metode proxy.

Contoh untuk proxy JDK Dynamic dan CGLib

Ref musim semi

Premraj
sumber
5

Dari dokumentasi Spring :

Spring AOP menggunakan proxy dinamis JDK atau CGLIB untuk membuat proxy untuk objek target yang diberikan. (JDK proxy dinamis lebih disukai setiap kali Anda punya pilihan).

Jika objek target yang akan diimplementasikan mengimplementasikan setidaknya satu antarmuka maka proxy dinamis JDK akan digunakan. Semua antarmuka yang diimplementasikan oleh tipe target akan diproksi. Jika objek target tidak mengimplementasikan antarmuka apa pun maka proksi CGLIB akan dibuat.

Jika Anda ingin memaksakan penggunaan proksi CGLIB (misalnya, untuk memproksi setiap metode yang ditentukan untuk objek target, bukan hanya yang diimplementasikan oleh antarmuka-nya) Anda dapat melakukannya. Namun, ada beberapa masalah yang perlu dipertimbangkan:

metode terakhir tidak dapat disarankan, karena mereka tidak dapat diganti.

Anda akan membutuhkan binari CGLIB 2 di classpath Anda, sedangkan proksi dinamis tersedia dengan JDK. Spring akan secara otomatis memperingatkan Anda ketika membutuhkan CGLIB dan kelas-kelas perpustakaan CGLIB tidak ditemukan di classpath.

Konstruktor objek proksi Anda akan dipanggil dua kali. Ini adalah konsekuensi alami dari model proksi CGLIB di mana subkelas dihasilkan untuk setiap objek yang diproksi. Untuk setiap instance proksi, dua objek dibuat: objek proksi aktual dan sebuah instance dari subkelas yang mengimplementasikan saran. Perilaku ini tidak diperagakan saat menggunakan proksi JDK. Biasanya, memanggil konstruktor tipe proksi dua kali, tidak menjadi masalah, karena biasanya hanya ada tugas yang terjadi dan tidak ada logika nyata yang diterapkan dalam konstruktor.

Taras Melnyk
sumber