อะไรคือความแตกต่างระหว่าง JDK dynamic proxy และ CGLib?


147

ในกรณีของProxy Design Patternความแตกต่างระหว่างDynamic Proxy ของ JDK กับ API การสร้างรหัสแบบไดนามิกของบุคคลที่สามเช่นCGLibคืออะไร?

อะไรคือความแตกต่างระหว่างการใช้ทั้งสองวิธีและเมื่อใดที่ควรจะใช้มากกว่ากัน?


3
รับรหัสได้ที่นี่: < gist.github.com/ksauzz/1563486 > ใน cglib คุณสามารถสร้างทั้ง class proxy และ interface proxy Spring ใช้ CGlib ตามค่าเริ่มต้นในขณะที่ AspectJ ใช้ Java proxy อ่านสิ่งนี้ด้วย: jnb.ociweb.com/jnb/jnbNov2005.html ;)
Subhadeep Ray

คำตอบ:


185

พร็อกซี JDK Dynamic สามารถใช้พร็อกซีได้เฉพาะตามอินเทอร์เฟซ (ดังนั้นคลาสเป้าหมายของคุณต้องใช้อินเทอร์เฟซ

CGLIB (และ javassist) สามารถสร้างพร็อกซีได้โดยการทำคลาสย่อย ในสถานการณ์สมมตินี้พร็อกซีกลายเป็นคลาสย่อยของคลาสเป้าหมาย ไม่จำเป็นต้องมีอินเตอร์เฟส

ดังนั้นผู้รับมอบฉันทะ Java Dynamic สามารถ proxy: public class Foo implements iFooที่ CGLIB สามารถ proxy:public class Foo

แก้ไข:

ฉันควรพูดถึงว่าเพราะ javassist และ CGLIB ใช้ proxy โดย subclassing นี่คือเหตุผลที่คุณไม่สามารถประกาศวิธีสุดท้ายหรือทำให้คลาสสุดท้ายเมื่อใช้เฟรมเวิร์กที่อาศัยสิ่งนี้ สิ่งนี้จะหยุดไลบรารีเหล่านี้ไม่ให้อนุญาตคลาสย่อยของคุณและแทนที่วิธีการของคุณ


ขอบคุณ .. !! แต่มันจะมีประโยชน์ถ้าคุณสามารถให้โค้ดตัวอย่างหนึ่ง (หรือลิงค์) แก่ฉันเพื่อแสดงให้เห็นถึงการใช้งานของคนอื่นมากกว่าในบางกรณี .. !!!
KDjava

1
โปรดทราบว่า JDK พร็อกซี่จะทำการพร็อกซีสำหรับ IFoo ไม่ใช่สำหรับ Foo ใด ๆ เลย มันค่อนข้างแตกต่างที่สำคัญ นอกจากนี้พร็อกซี cglib เป็นคลาสย่อยเต็ม - ใช้ประโยชน์จากสิ่งนั้น! ใช้ตัวกรองเพื่อใช้วิธีพร็อกซีเท่านั้นที่คุณใส่ใจและใช้คลาสที่สร้างขึ้นโดยตรง
lscoughlin

9
ควรสังเกตว่าการสร้างคลาสย่อย CGLib นั้นจำเป็นต้องมีความรู้เพียงพอเกี่ยวกับซุปเปอร์คลาสเพื่อให้สามารถเรียกตัวสร้างที่ถูกต้องด้วย args ที่ถูกต้อง ไม่เหมือนกับ proxy ที่ใช้อินเตอร์เฟสซึ่งไม่สนใจเกี่ยวกับ Constructor สิ่งนี้ทำให้การทำงานกับ CGLib proxies น้อย "อัตโนมัติ" กว่า JDK proxies ความแตกต่างอีกอย่างคือในราคา "กอง" พร็อกซี JDK จะเกิดเฟรมสแต็กเพิ่มเติมต่อการโทรเสมอในขณะที่ CGLib อาจไม่เสียค่าใช้จ่ายสแต็กเฟรมพิเศษใด ๆ สิ่งนี้มีความเกี่ยวข้องมากขึ้นเรื่อย ๆ เมื่อแอพพลิเคชั่นมีความซับซ้อนมากขึ้นเท่านั้น
เรย์

1
cglib ไม่สามารถใช้วิธีสุดท้ายในพร็อกซีได้ แต่จะไม่โยนข้อยกเว้นgist.github.com/mhewedy/7345403cfa52e6f47563f8a204ec0e80
Muhammad Hewedy

ใช่ CGLIB ไม่สนใจวิธีการขั้นสุดท้าย
yashjain12yj

56

ความแตกต่างในฟังก์ชั่น

  • ผู้รับมอบฉันทะ JDK อนุญาตให้มีการใช้ชุดของการเชื่อมต่อใด ๆ ในขณะ Objectsubclassing วิธีการอินเตอร์เฟซใดบวกObject::hashCode, Object::equalsและจะถูกส่งต่อไปยังแล้วObject::toString InvocationHandlerนอกจากนี้ยังjava.lang.reflect.Proxyมีการใช้อินเตอร์เฟสไลบรารีมาตรฐาน

  • cglib ช่วยให้คุณสามารถใช้ชุดของอินเทอร์เฟซใด ๆ ในขณะที่คลาสย่อยที่ไม่ใช่คลาสสุดท้าย นอกจากนี้วิธีการสามารถแทนที่เลือกเช่นไม่ใช่วิธีการที่เป็นนามธรรมทั้งหมดจะต้องมีการสกัดกั้น นอกจากนี้ยังมีวิธีการใช้วิธีที่แตกต่างกัน นอกจากนี้ยังมีInvocationHandlerระดับ (ในแพคเกจที่แตกต่างกัน) MethodInterceptorแต่ก็ยังจะช่วยให้การเรียกวิธีซุปเปอร์โดยใช้ไล่สูงขึ้นเป็นตัวอย่างเช่น นอกจากนี้ CGLIB FixedValueสามารถปรับปรุงประสิทธิภาพการทำงานโดยเซพชั่นพิเศษเช่น ผมเคยเขียนบทสรุปของการไล่ที่แตกต่างกันสำหรับ CGLIB

ความแตกต่างด้านประสิทธิภาพ

ผู้รับมอบฉันทะ JDK InvocationHandlerจะดำเนินการค่อนข้างไร้เดียงสาที่มีเพียงการสกัดกั้นผู้มอบหมายงานหนึ่ง สิ่งนี้ต้องการวิธีการเสมือนที่ส่งไปยังการนำไปใช้งานซึ่งไม่สามารถ inline ได้ตลอดเวลา Cglib อนุญาตให้สร้างโค้ดไบต์พิเศษซึ่งบางครั้งสามารถปรับปรุงประสิทธิภาพได้ นี่คือการเปรียบเทียบบางส่วนสำหรับการใช้อินเทอร์เฟซกับวิธีการ 18 สตับ:

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

เวลาถูกบันทึกไว้ในหน่วยนาโนวินาทีโดยมีค่าเบี่ยงเบนมาตรฐานเป็นเครื่องหมายวงเล็บ คุณสามารถค้นหารายละเอียดเพิ่มเติมเกี่ยวกับเกณฑ์มาตรฐานในบทช่วยสอนของ Byte Buddy ที่ Byte Buddy เป็นทางเลือกที่ทันสมัยกว่า cglib นอกจากนี้โปรดทราบว่า cglib ไม่อยู่ภายใต้การพัฒนาที่ใช้งานได้อีกต่อไป


2
เหตุใดเอกสารเกี่ยวกับฤดูใบไม้ผลิจึงให้ประโยชน์แก่ JDK การใช้พร็อกซีมากกว่า cglib เมื่อได้รับประโยชน์ด้านประสิทธิภาพของรุ่นหลัง docs.spring.io/spring/docs/2.5.x/reference/…
P4ndaman

2
Cglib เป็นพึ่งพาภายนอกและไม่ได้รับการสนับสนุนในปัจจุบัน การใช้ซอฟต์แวร์ของ บริษัท อื่นเป็นการเดิมพันที่ดีที่สุดเมื่อมีคนเพียงไม่กี่คนที่สามารถพึ่งพาได้
Rafael Winterhalter

ในบล็อกของคุณคุณพูดว่า: "อย่างไรก็ตามคุณควรระวังเมื่อเรียกวิธีการบนวัตถุพร็อกซีที่มาพร้อมกับวิธีการ InvocationHandler # invoke การโทรทั้งหมดในวิธีนี้จะถูกส่งด้วย InvocationHandler เดียวกันและอาจส่งผลให้เกิดการวนซ้ำไม่รู้จบ ." คุณหมายถึงอะไร
Koray Tugay

หากคุณเรียกใช้เมธอดบนวัตถุพร็อกซีการโทรใด ๆ จะถูกส่งผ่านตัวจัดการการร้องขอของเรา หากการเรียกตัวจัดการการเรียกใด ๆ มอบหมายให้การเรียกไปยังวัตถุการเรียกซ้ำเกิดขึ้น
Rafael Winterhalter

สวัสดีราฟาเอล, ข้อความที่ไม่เกี่ยวข้องกับคำตอบของคุณผมกระตุกคุณเกี่ยวกับการแก้ไขทำ 5 ปีที่ผ่านมา ในฐานะที่เป็นCGLIBเห็นได้ชัดว่ายังคงมีการกระทำใน 2019 และไม่ได้แสดงใด ๆการพัฒนาที่ถูกจับกุมใน README ของผมได้ลบออกคำสั่งของคุณจากข้อความที่ตัดตอนมาแท็ก อย่าลังเลที่จะปรับปรุงคำอธิบายแท็ก / ข้อความที่ตัดตอนมาหากมีสิ่งใดที่เกี่ยวข้องกับการพูดถึง
Cœur

28

พร็อกซี่แบบไดนามิก:ใช้งานแบบไดนามิกของอินเตอร์เฟซที่ใช้งานจริงโดยใช้JDK สะท้อน API

ตัวอย่าง: Spring ใช้พรอกซีแบบไดนามิกสำหรับการทำธุรกรรมดังนี้:

ป้อนคำอธิบายรูปภาพที่นี่

พร็อกซีที่สร้างขึ้นมาอยู่ด้านบนของ bean มันเพิ่มพฤติกรรมข้ามชาติในถั่ว ที่นี่พร็อกซีสร้างแบบไดนามิกตอนรันไทม์โดยใช้ JDK Reflection API

เมื่อแอปพลิเคชันหยุดทำงานพร็อกซีจะถูกทำลายและเราจะมีอินเตอร์เฟสและ bean ในระบบไฟล์เท่านั้น


ในตัวอย่างข้างต้นเรามีส่วนต่อประสาน แต่ในส่วนใหญ่ของการใช้อินเตอร์เฟซที่ไม่ดีที่สุด ดังนั้น bean ไม่ได้ใช้อินเตอร์เฟสในกรณีนั้นเราใช้การสืบทอด:

ป้อนคำอธิบายรูปภาพที่นี่

เพื่อสร้างพร็อกซี่ดังกล่าวสปริงใช้ไลบรารีบุคคลที่สามที่เรียกว่า CGLIB

CGLIB ( CบทกวีG eneration Lib Rary) จะถูกสร้างขึ้นบนASMนี้ส่วนใหญ่จะใช้สร้างถั่วขยายพร็อกซี่และเพิ่มพฤติกรรมถั่วในวิธีการพร็อกซี่

ตัวอย่างสำหรับ JDK Dynamic proxy และ CGLib

สปริงอ้างอิง


5

จากเอกสารฤดูใบไม้ผลิ :

Spring AOP ใช้ JDK dynamic proxies หรือ CGLIB เพื่อสร้าง proxy สำหรับวัตถุเป้าหมายที่กำหนด (ต้องการพร็อกซี JDK แบบไดนามิกทุกครั้งที่คุณมีตัวเลือก)

หากวัตถุเป้าหมายที่จะใช้พร็อกซีนำไปใช้อย่างน้อยหนึ่งอินเตอร์เฟสจะใช้พร็อกซี JDK แบบไดนามิก อินเทอร์เฟซทั้งหมดที่ใช้งานตามประเภทเป้าหมายจะถูกนำเสนอ หากวัตถุเป้าหมายไม่ได้ใช้อินเตอร์เฟสใด ๆ ดังนั้นพร็อกซี CGLIB จะถูกสร้างขึ้น

หากคุณต้องการบังคับใช้การใช้ CGLIB proxying (ตัวอย่างเช่นเพื่อให้พร็อกซีทุกวิธีที่กำหนดไว้สำหรับวัตถุเป้าหมาย อย่างไรก็ตามมีบางประเด็นที่ต้องพิจารณา:

ไม่สามารถแนะนำวิธีการขั้นสุดท้ายได้เนื่องจากไม่สามารถ overriden ได้

คุณจะต้องใช้ CGLIB 2 ไบนารีบน classpath ของคุณในขณะที่พร็อกซีไดนามิกพร้อมใช้งานกับ JDK Spring จะเตือนคุณโดยอัตโนมัติเมื่อต้องการ CGLIB และไม่พบคลาสไลบรารี CGLIB ใน classpath

คอนสตรัคเตอร์ของวัตถุพร็อกซีของคุณจะถูกเรียกสองครั้ง นี่เป็นผลลัพธ์ตามธรรมชาติของโมเดลพร็อกซี CGLIB โดยคลาสย่อยจะถูกสร้างขึ้นสำหรับแต่ละวัตถุพร็อกซี สำหรับแต่ละอินสแตนซ์พร็อกซีจะสร้างวัตถุสองรายการ: วัตถุพร็อกซีจริงและอินสแตนซ์ของคลาสย่อยที่ใช้คำแนะนำ ลักษณะการทำงานนี้จะไม่แสดงเมื่อใช้พรอกซี JDK โดยปกติแล้วการเรียกตัวสร้างของชนิดพร็อกซีสองครั้งไม่ใช่ปัญหาเนื่องจากมักจะมีเพียงการมอบหมายที่เกิดขึ้นและไม่มีการใช้ตรรกะจริงในตัวสร้าง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.