กุญแจสำคัญในการใช้งานพร็อกซีแบบไดนามิกใน Java คือสองสิ่งนี้: พร็อกซีและ InvocationHandler เริ่มต้นด้วยวิธีการเรียกใช้ในอินเทอร์เฟซ InvocationHandler และอธิบายสั้น ๆ ว่า Java ใช้พร็อกซีแบบไดนามิกอย่างไร
ขั้นแรกรูปแบบที่สมบูรณ์ของวิธีการเรียกใช้ดังนี้:
วัตถุสาธารณะเรียกใช้ (พร็อกซีวัตถุวิธีการ, วัตถุ [] args) โยน {method.invoke (obj, args); return null;}ก่อนอื่นให้เดาว่าวิธีการเป็นวิธีที่เรียกว่านั่นคือวิธีการที่ต้องดำเนินการ Args เป็นพารามิเตอร์ของวิธีการ; พร็อกซีพารามิเตอร์นี้คืออะไร? การดำเนินการข้างต้นของวิธีการเรียกใช้ () เป็นรูปแบบที่ค่อนข้างมาตรฐาน เราเห็นว่าไม่มีการใช้พารามิเตอร์พร็อกซีที่นี่ ตรวจสอบคำอธิบายของพร็อกซีในเอกสาร JDK ดังต่อไปนี้:
การเรียกใช้เมธอดบนพร็อกซีอินสแตนซ์ผ่านหนึ่งในอินเทอร์เฟซพร็อกซีของมันจะถูกส่งไปยังวิธีการเรียกใช้ของตัวจัดการการเรียกร้องของอินสแตนซ์ผ่านอินสแตนซ์พร็อกซี, java.lang.reflect.Method วัตถุที่ระบุวิธีการที่ถูกเรียกใช้และอาร์เรย์ของวัตถุประเภทที่มีข้อโต้แย้ง
จากนี้เราสามารถรู้ได้ว่าการคาดเดาข้างต้นนั้นถูกต้องและเราก็รู้ว่าพารามิเตอร์พร็อกซีเป็นอินสแตนซ์ของคลาสพร็อกซี
เพื่อความสะดวกในการอธิบายนี่คือตัวอย่างง่ายๆในการใช้งานพร็อกซีแบบไดนามิก
// บทคัดย่อบทบาท (พร็อกซีแบบไดนามิกสามารถใช้งานพร็อกซีอินเทอร์เฟซเท่านั้น) หัวเรื่องสาธารณะ {คำขอโมฆะสาธารณะ (); - // บทบาทจริง: ใช้วิธีการร้องขอ () ของวิชาสาธารณะคลาสสาธารณะ realsubject ใช้หัวข้อ {คำขอโมฆะสาธารณะ () {system.out.println ("จากเรื่องจริง"); - // ใช้ InvocationHandler คลาสสาธารณะ DynamicsUbject ใช้งาน InvocationHandler {วัตถุส่วนตัว obj; // นี่คือประโยชน์ของพร็อกซีแบบไดนามิก วัตถุที่ห่อหุ้มเป็นประเภทวัตถุและยอมรับวัตถุของ DynamicsUbject สาธารณะประเภทใด ๆ () {} DynamicsUbject สาธารณะ (Object OBJ) {this.obj = obj; } // วิธีนี้ไม่ใช่สิ่งที่เราแสดงให้เรียกวัตถุสาธารณะ Invoke (พร็อกซีวัตถุวิธีเมธอด, วัตถุ [] args) โยน {system.out.println ("ก่อนโทร" + วิธี); method.invoke (obj, args); System.out.println ("หลังจากโทร" + วิธี); คืนค่า null; - // ไคลเอนต์: สร้างอินสแตนซ์พร็อกซีและเรียกร้องคำขอ () เมธอดไคลเอ็นต์คลาสสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) โยน {// // todo วิธีการที่สร้างขึ้นอัตโนมัติ stub subject rs = ใหม่ realsubject (); // ระบุคลาสพร็อกซี คลาส <s?> cls = rs.getClass (); // ต่อไปนี้เป็นการสร้างพร็อกซีเรื่องแบบครั้งเดียว = (หัวเรื่อง) Proxy.newProxyinstance (cls.getClassloader (), cls.getInterfaces (), ds); // ที่นี่คุณสามารถพิสูจน์ได้ว่าหัวเรื่องเป็นอินสแตนซ์ของพร็อกซีโดยการเรียกใช้ผลลัพธ์ อินสแตนซ์นี้ใช้ระบบอินเทอร์เฟซแบบหัวเรื่อง. // ที่นี่คุณจะเห็นว่าคลาสคลาสของเรื่องคือ $ proxy0 คลาส $ proxy0 นี้สืบทอดพร็อกซีและใช้ระบบอินเทอร์เฟซเรื่อง System.out.println ("คลาสคลาสของเรื่องคือ:"+subject.getClass (). toString ()); System.out.print ("คุณสมบัติในเรื่องคือ:"); ฟิลด์ [] ฟิลด์ = subject.getClass (). getDeclaredFields (); สำหรับ (ฟิลด์ f: ฟิลด์) {system.out.print (f.getName ()+","); } วิธีการใน system.out.print ("/n"+"หัวเรื่องคือ:"); วิธี [] method = subject.getClass (). getDeclaredMethods (); สำหรับ (เมธอด m: เมธอด) {system.out.print (m.getName ()+","); } คลาสหลักของ System.out.println ("/n"+"หัวเรื่องคือ:"+subject.getClass (). getSuperclass ()); System.out.print ("/n"+"ใช้อินเทอร์เฟซ:"); คลาส <?> [] อินเทอร์เฟซ = subject.getClass (). getInterfaces (); สำหรับ (คลาส <?> i: อินเตอร์เฟส) {system.out.print (i.getName ()+","); } system.out.println ("/n/n"+"ผลการเรียกใช้คือ:"); หัวเรื่อง. request (); - ผลการดำเนินการมีดังนี้: ชื่อแพ็คเกจถูกละไว้ที่นี่ *** แทน
จริง
คลาสคลาสของเรื่องคือ: คลาส $ proxy0
คุณสมบัติในเรื่องคือ: m1, m3, m0, m2,
วิธีการในเรื่องคือ: คำขอ, hashcode, equals, toString,
คลาสหลักของหัวเรื่องคือ: คลาส java.lang.reflect.proxy
อินเทอร์เฟซที่ดำเนินการโดยหัวเรื่องคือ: cn.edu.ustc.dynamicproxy.subject
ผลการดำเนินการคือ:
ก่อนที่จะโทรหานามธรรมนามธรรมสาธารณะ *** subject.request ()
จากเรื่องจริง
หลังจากโทรไปที่นามธรรมนามธรรมสาธารณะ *** subject.request ()
PS: ข้อมูลเกี่ยวกับผลลัพธ์นี้สำคัญมากอย่างน้อยสำหรับฉัน เพราะสาเหตุของอาการวิงเวียนศีรษะของฉันในพร็อกซีแบบไดนามิกคือฉันเข้าใจผิดเรื่องข้างต้น request () อย่างน้อยฉันก็สับสนกับพื้นผิวและไม่พบการเชื่อมต่อระหว่างเรื่องกับพร็อกซี ฉันเคยสับสนเกี่ยวกับวิธีการร้องขอการโทรครั้งสุดท้าย () เชื่อมต่อกับ ravoke () และเรียกร้องให้มีการร้องขออย่างไร ในความเป็นจริง True และ Class $ Proxy0 ด้านบนสามารถแก้ปัญหาได้มากมายและควบคู่ไปกับซอร์สโค้ดของ $ proxy0 ที่จะกล่าวถึงด้านล่างมันสามารถแก้ข้อสงสัยของพร็อกซีแบบไดนามิกได้อย่างสมบูรณ์
จากรหัสและผลลัพธ์ข้างต้นเราจะเห็นว่าเราไม่ได้เรียกวิธีการเรียกใช้ () ตามที่แสดง แต่วิธีนี้ดำเนินการ มาวิเคราะห์กระบวนการทั้งหมดด้านล่าง:
ตัดสินจากรหัสในลูกค้าคุณสามารถใช้วิธีการใหม่ของ Newproxyinstance เป็นความก้าวหน้า ก่อนอื่นให้ดูที่ซอร์สโค้ดของวิธี newproxyinstance ในคลาสพร็อกซี:
วัตถุสแตติกสาธารณะ newproxyinstance (classloader loader, คลาส <s?> [] อินเตอร์เฟส, InvocationHandler h) พ่น unglegalargumentException {ถ้า (h == null) {โยน nullpointerexception ใหม่ (); } / * * ค้นหาหรือสร้างคลาสพร็อกซีที่ออกแบบมา */ class cl = getProxyClass (โหลดเดอร์อินเตอร์เฟส); / * * เรียกใช้คอนสตรัคเตอร์ด้วยตัวจัดการการเรียกร้องที่ออกแบบมา * / ลอง { / * * ซอร์สโค้ดพร็อกซีมีคำจำกัดความต่อไปนี้: * คลาสคงที่ส่วนตัวสุดท้าย [] constructorParams = {InvocationHandler.class}; * ข้อเสียคือวิธีตัวสร้างที่มีพารามิเตอร์อย่างเป็นทางการของประเภท InvocationHandler*/ constructor cons = cl.getConstructor (ConstructorParams); return (Object) cons.newinstance (วัตถุใหม่ [] {h}); } catch (nosuchmethodexception e) {โยนใหม่ภายใน (e.toString ()); } catch (unglegalAccessException e) {โยน new InternalError (e.toString ()); } catch (InstantiationException e) {โยนใหม่ภายใน (e.toString ()); } catch (InvocationTargetException E) {โยน InternalError ใหม่ (e.toString ()); - Proxy.newProxyInstance (classloader loader, คลาส <s?> [] อินเตอร์เฟส, InvocationHandler H) สิ่งต่อไปนี้
(1) เรียกใช้เมธอด getProxyClass (โหลดเดอร์อินเตอร์เฟส) ตามพารามิเตอร์โหลดเดอร์และอินเตอร์เฟสสร้างคลาสพร็อกซี $ proxy0. $ proxy0 คลาสใช้อินเตอร์เฟสอินเตอร์เฟสและสืบทอดคลาสพร็อกซี
(2) อินสแตนซ์ $ proxy0 และ pass dynamicsubject ในคอนสตรัคเตอร์จากนั้น $ proxy0 เรียกตัวสร้างของพร็อกซีคลาสแม่และกำหนดค่าให้ H ดังนี้:
พร็อกซีคลาส {InvocationHandler h = null; พร็อกซีที่ได้รับการป้องกัน (InvocationHandler H) {this.h = h; -มาดูซอร์สโค้ดที่สืบทอด $ proxy0 ของ Proxy:
ระดับสุดท้ายระดับสุดท้าย $ PROXY0 ขยายการใช้งานพร็อกซีใช้เรื่อง {วิธีการคงที่ส่วนตัว M1; วิธีการคงที่ส่วนตัว M0; วิธีการคงที่ส่วนตัว M3; วิธีการคงที่ส่วนตัว M2; คงที่ {ลอง {m1 = class.forName ("java.lang.Object"). getMethod ("เท่ากับ", คลาสใหม่ [] {class.forname ("java.lang.Object")}); m0 = class.forname ("java.lang.Object"). getMethod ("hashCode", คลาสใหม่ [0]); m3 = class.forName ("***. RealSubject"). getMethod ("คำขอ", คลาสใหม่ [0]); m2 = class.forname ("java.lang.Object"). getMethod ("toString", คลาสใหม่ [0]); } catch (nosuchmethodexception nosuchmethodexception) {โยน nosuchmethoderror ใหม่ (nosuchmethodexception.getMessage ()); } catch (classnotFoundException classnotFoundException) {โยน noclassDeffoundError ใหม่ (classnotFoundException.getMessage ()); }} // สาธารณะคงที่ $ proxy0 (InvocationHandler InvocationHandler) {super (InvocationHandler); } @Override Public Final Boolean เท่ากับ (Object obj) {ลอง {return ((บูลีน) super.h.invoke (นี่, m1, วัตถุใหม่ [] {obj})) .booleanvalue (); } catch (throwable throwable) {โยน undeclaredthrowableexception ใหม่ (throwable); }} @Override สาธารณะสุดท้าย int hashCode () {ลอง {return ((จำนวนเต็ม) super.h.invoke (นี่, m0, null)). intvalue (); } catch (throwable throwable) {โยน undeclaredthrowableexception ใหม่ (throwable); }} คำขอโมฆะสุดท้ายสาธารณะ () {ลอง {super.h.invoke (นี่, m3, null); กลับ; } catch (ข้อผิดพลาด e) {} catch (throwable throwable) {โยน undeclaredthrowableException ใหม่ (throwable); }} @Override Public Final String toString () {ลอง {return (string) super.h.invoke (นี่, m2, null); } catch (throwable throwable) {โยน undeclaredthrowableexception ใหม่ (throwable); -จากนั้นโยนอินสแตนซ์ $ proxy0 ที่ได้ลงในหัวเรื่องและกำหนดการอ้างอิงให้กับเรื่อง เมื่อมีการดำเนินการเมธอด request () เมธอดคำขอ () ในคลาส $ proxy0 เรียกว่าและวิธีการเรียกใช้ () ของ H ในพร็อกซีคลาสแม่เรียกว่า นั่นคือ InvocationHandler.invoke ()
PS: 1. สิ่งหนึ่งที่ควรทราบคือวิธี GetProxyClass ในคลาสพร็อกซีส่งคืนคลาสของพร็อกซี เหตุผลนี้คือฉันทำผิดระดับต่ำในตอนแรกโดยคิดว่าการกลับมาคือ "คลาสคลาสของคลาสพร็อกซี"-! ขอแนะนำให้ดูที่ซอร์สโค้ดของ GetProxyclass ซึ่งยาวมาก = -
2. จากซอร์สโค้ดของ $ proxy0 จะเห็นได้ว่าคลาสพร็อกซีแบบไดนามิกไม่เพียง แต่พร็อกซีวิธีการในอินเทอร์เฟซที่กำหนดจอแสดงผล แต่ยังพร็อกซีที่สืบทอดมาสามวิธีของค่าเท่ากับ (), hashcode ()
ถาม: จนถึงตอนนี้ยังมีคำถาม พารามิเตอร์แรกในเมธอด Invoke เป็นอินสแตนซ์ของพร็อกซี (เพื่อความแม่นยำอินสแตนซ์ของ $ proxy0 ถูกนำมาใช้ในที่สุด) แต่การใช้งานคืออะไร? หรือฟังก์ชั่นปรากฏในโปรแกรมอย่างไร
ตอบ: จากระดับปัจจุบันของฉันพารามิเตอร์พร็อกซีนี้ไม่มีผล ในกลไกพร็อกซีแบบไดนามิกทั้งหมดพารามิเตอร์พร็อกซีของเมธอด Invoke ใน InvocationHandler ไม่ได้ใช้ พารามิเตอร์ที่ส่งผ่านเป็นอินสแตนซ์ของคลาสพร็อกซี ฉันคิดว่ามันอาจเป็นการให้โปรแกรมเมอร์ใช้การสะท้อนในวิธีการเรียกใช้เพื่อรับข้อมูลบางอย่างเกี่ยวกับคลาสพร็อกซี
สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้เกี่ยวกับวิธีการเรียกใช้ () ใน InvocationHandler ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน เพื่อนที่สนใจสามารถอ้างถึงเว็บไซต์นี้ต่อไปได้:
คำอธิบายโดยละเอียดเกี่ยวกับพร็อกซีสปริงคงที่และรหัสพร็อกซีแบบไดนามิก
ตัวอย่างวิธีการฉีดตามกรอบสปริง
การเขียนโปรแกรม Java ของตัวอย่างการเข้าสู่ระบบ SpringMVC Simple Login
หากมีข้อบกพร่องใด ๆ โปรดฝากข้อความไว้เพื่อชี้ให้เห็น