1. คำจำกัดความของโหมดพร็อกซี
จัดเตรียมวัตถุด้วยวัตถุพร็อกซีและวัตถุพร็อกซีควบคุมการเข้าถึงวัตถุดั้งเดิมนั่นคือไคลเอนต์ไม่ได้จัดการกับวัตถุดั้งเดิมโดยตรง แต่จะจัดการกับวัตถุดั้งเดิมผ่านวัตถุพร็อกซีโดยตรง
ตัวอย่างของรูปแบบพร็อกซีที่มีชื่อเสียงคือการนับการอ้างอิง: เมื่อจำเป็นต้องใช้สำเนาวัตถุที่ซับซ้อนหลายชุดรูปแบบพร็อกซีสามารถรวมกับโหมด Meta เพื่อลดปริมาณหน่วยความจำ วิธีการทั่วไปคือการสร้างวัตถุที่ซับซ้อนและพร็อกซีหลายรายการแต่ละพร็อกซีอ้างถึงวัตถุดั้งเดิม การดำเนินการที่ดำเนินการกับตัวแทนจะถูกส่งต่อไปยังวัตถุต้นฉบับ เมื่อไม่มีตัวแทนทั้งหมดวัตถุที่ซับซ้อนจะถูกลบออก
มันง่ายที่จะเข้าใจโมเดลพร็อกซี แต่ในความเป็นจริงมีโมเดลพร็อกซีในชีวิต:
เราสามารถซื้อตั๋วรถไฟได้ที่สถานีรถไฟ แต่เราสามารถซื้อได้ที่สำนักงานขายตั๋วรถไฟ สำนักงานขายตั๋วรถไฟที่นี่เป็นตัวแทนสำหรับการซื้อตั๋วที่สถานีรถไฟ นั่นคือเราออกคำขอซื้อตั๋วที่ร้านขาย ร้านขายจะส่งคำขอไปยังสถานีรถไฟและสถานีรถไฟจะส่งการตอบกลับที่ประสบความสำเร็จในการซื้อไปยังร้านขายและร้านขายจะบอกคุณอีกครั้ง
อย่างไรก็ตามสามารถซื้อตั๋วได้ที่ร้านขายเท่านั้น แต่ไม่คืนเงินในขณะที่สามารถซื้อตั๋วได้ที่สถานีรถไฟดังนั้นการดำเนินงานที่ได้รับการสนับสนุนโดยตัวแทนอาจแตกต่างจากวัตถุที่ได้รับมอบหมาย
ให้ฉันยกตัวอย่างอีกตัวอย่างที่คุณจะพบเมื่อเขียนโปรแกรม:
หากมีโครงการที่มีอยู่ (คุณไม่มีซอร์สโค้ดคุณสามารถเรียกได้เท่านั้น) ที่สามารถเรียก Int Compute (String Exp1) เพื่อใช้การคำนวณนิพจน์ต่อท้าย หากคุณต้องการใช้โครงการนี้เพื่อใช้การคำนวณนิพจน์ Infix คุณสามารถเขียนคลาสพร็อกซีและกำหนดการคำนวณ (String exp2) พารามิเตอร์ exp2 นี้เป็นนิพจน์ infix ดังนั้นคุณต้องแปลงนิพจน์ Infix เป็นนิพจน์ต่อท้าย (ประมวลผลล่วงหน้า) ก่อนที่จะเรียกการคำนวณ () ของโครงการที่มีอยู่แล้วเรียกการคำนวณ () ของโครงการที่มีอยู่ แน่นอนคุณยังสามารถรับค่าส่งคืนและดำเนินการอื่น ๆ เช่นการบันทึกไฟล์ (postprocess) กระบวนการนี้ใช้โหมดพร็อกซี
เมื่อใช้คอมพิวเตอร์คุณจะพบแอปพลิเคชันโหมดพร็อกซี:
ระยะไกลพร็อกซี: เราไม่สามารถเข้าถึง Facebook ได้เนื่องจาก GFW ในประเทศจีน เราสามารถเข้าถึงได้โดยการเรียกดูผนัง (ตั้งค่าพร็อกซี) กระบวนการเข้าถึงคือ:
(1) ผู้ใช้ส่งคำขอ HTTP ไปยังพร็อกซี
(2) พร็อกซีส่งคำขอ HTTP ไปยังเว็บเซิร์ฟเวอร์
(3) เว็บเซิร์ฟเวอร์ส่งการตอบกลับ HTTP ไปยังพร็อกซี
(4) พร็อกซีส่งการตอบกลับ HTTP กลับไปยังผู้ใช้
2. พร็อกซีแบบคงที่
พร็อกซีแบบคงที่ที่เรียกว่าหมายถึงคลาสพร็อกซีถูกสร้างขึ้นในระหว่างขั้นตอนการรวบรวมเพื่อดำเนินการชุดการดำเนินงานบนวัตถุพร็อกซี ต่อไปนี้เป็นแผนภาพคลาสโครงสร้างของรูปแบบพร็อกซี:
1. ผู้เข้าร่วมในโมเดลพร็อกซี
มีสี่บทบาทในโหมดพร็อกซี:
อินเทอร์เฟซหัวข้อ: นั่นคืออินเทอร์เฟซพฤติกรรมที่ใช้โดยคลาสพร็อกซี
วัตถุเป้าหมาย: นั่นคือวัตถุที่เป็นพร็อกซี
พร็อกซีออบเจ็กต์: ไคลเอนต์พร็อกซีที่ใช้ในการห่อหุ้มคลาสหัวข้อจริงคือโครงสร้างไดอะแกรมคลาสของรูปแบบพร็อกซี:
2. แนวคิดสำหรับการใช้โมเดลตัวแทน
ทั้งวัตถุพร็อกซีและวัตถุเป้าหมายใช้อินเทอร์เฟซพฤติกรรมเดียวกัน
คลาสพร็อกซีและคลาสเป้าหมายใช้ตรรกะอินเตอร์เฟสแยกกัน
ยกตัวอย่างวัตถุเป้าหมายในตัวสร้างของคลาสพร็อกซี
การเรียกอินเทอร์เฟซพฤติกรรมของวัตถุเป้าหมายในคลาสพร็อกซี
หากลูกค้าต้องการเรียกอินเทอร์เฟซพฤติกรรมของวัตถุเป้าหมายมันสามารถทำงานผ่านคลาสพร็อกซีเท่านั้น
3. ตัวอย่างของพร็อกซีแบบคงที่
ต่อไปนี้เป็นตัวอย่างการโหลดขี้เกียจเพื่อแสดงพร็อกซีแบบคงที่ เมื่อเราเริ่มระบบบริการอาจใช้เวลานานในการโหลดคลาสที่แน่นอน เพื่อให้ได้ประสิทธิภาพที่ดีขึ้นเมื่อเริ่มระบบเรามักจะไม่เริ่มต้นคลาสที่ซับซ้อนนี้ แต่แทนที่จะเริ่มต้นคลาสพร็อกซี สิ่งนี้จะแยกวิธีการใช้ทรัพยากรที่ใช้เวลานานโดยใช้พร็อกซีสำหรับการแยกซึ่งสามารถเพิ่มความเร็วความเร็วในการเริ่มต้นของระบบและลดเวลารอของผู้ใช้
กำหนดอินเทอร์เฟซหัวข้อ
หัวเรื่องสาธารณะ {โมฆะสาธารณะ Sayshello (); โมฆะสาธารณะ Saysgoodbye ();} กำหนดคลาสเป้าหมายและใช้อินเทอร์เฟซหัวข้อ
Public Class RealSubject ใช้เรื่อง {โมฆะสาธารณะ Sayshello () {System.out.println ("Hello World"); } โมฆะสาธารณะ saysgoodbye () {system.out.println ("Goodbye World"); - กำหนดคลาสพร็อกซีเพื่อพร็อกซีวัตถุเป้าหมาย
คลาสสาธารณะ StaticProxy ใช้เรื่อง {ส่วนตัว realsubject realsubject = null; สาธารณะ staticproxy () {} โมฆะสาธารณะ sayshello () {// มันถูกโหลดในเวลานั้น, ขี้เกียจโหลดถ้า (realsubject == null) {realsubject = ใหม่ realsubject (); } realsubject.sayhello (); } // เมธอด saygoodbye เหมือนกัน ... } กำหนดลูกค้า
ไคลเอนต์ระดับสาธารณะ {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {staticproxy sp = new StaticProxy (); sp.sayhello (); sp.saygoodbye (); -ข้างต้นเป็นตัวอย่างการทดสอบอย่างง่ายของพร็อกซีแบบคงที่ มันอาจไม่รู้สึกว่ามีประโยชน์ อย่างไรก็ตามนี่ไม่ใช่กรณี การใช้พร็อกซีเรายังสามารถแปลงวิธีการวัตถุเป้าหมายได้ ตัวอย่างเช่นชุดการเชื่อมต่อถูกสร้างขึ้นในพูลเชื่อมต่อฐานข้อมูล เพื่อให้แน่ใจว่าการเชื่อมต่อจะเปิดไม่นานการเชื่อมต่อเหล่านี้แทบจะไม่เคยปิด อย่างไรก็ตามเรามักจะมีนิสัยในการปิดการเชื่อมต่อแบบเปิด ด้วยวิธีนี้เราสามารถใช้โหมดพร็อกซีเพื่อทำซ้ำวิธีปิดในอินเตอร์เฟสการเชื่อมต่อและเปลี่ยนเป็นรีไซเคิลลงในพูลการเชื่อมต่อฐานข้อมูลแทนที่จะใช้วิธีการเชื่อมต่อ#ปิดจริง มีตัวอย่างอื่น ๆ อีกมากมายและคุณต้องสัมผัสกับพวกเขาด้วยตัวเอง
3. ตัวแทนไดนามิก
พร็อกซีแบบไดนามิกหมายถึงการสร้างคลาสพร็อกซีแบบไดนามิกที่รันไทม์ นั่นคือไบต์ของคลาสพร็อกซีจะถูกสร้างและโหลดที่รันไทม์ไปยังคลาสโหลดของพร็อกซีปัจจุบัน เมื่อเปรียบเทียบกับคลาสการประมวลผลแบบคงที่คลาสไดนามิกมีประโยชน์มากมาย
ไม่จำเป็นต้องเขียนคลาสการห่อหุ้มที่เหมือนกันอย่างสมบูรณ์สำหรับหัวข้อจริง หากมีวิธีการมากมายในอินเทอร์เฟซหัวข้อมันก็เป็นปัญหาในการเขียนวิธีพร็อกซีสำหรับแต่ละอินเตอร์เฟส หากอินเทอร์เฟซเปลี่ยนแปลงชุดรูปแบบจริงและคลาสพร็อกซีจะต้องได้รับการแก้ไขซึ่งไม่เอื้อต่อการบำรุงรักษาระบบ
การใช้วิธีการสร้างพร็อกซีแบบไดนามิกบางอย่างสามารถกำหนดตรรกะการดำเนินการของคลาสพร็อกซีที่รันไทม์ซึ่งจะเป็นการปรับปรุงความยืดหยุ่นของระบบอย่างมาก
มีหลายวิธีในการสร้างพร็อกซีแบบไดนามิก: JDK มาพร้อมกับพร็อกซีแบบไดนามิก, cglib, javassist ฯลฯ วิธีการเหล่านี้มีข้อดีและข้อเสียของตัวเอง บทความนี้ส่วนใหญ่สำรวจการใช้พร็อกซีแบบไดนามิกและการวิเคราะห์ซอร์สโค้ดใน JDK
นี่คือตัวอย่างที่จะอธิบายการใช้งานของพร็อกซีแบบไดนามิกใน JDK:
คลาสสาธารณะ DynamicProxy ใช้ InvocationHandler {ส่วนตัว realSubject = null; วัตถุสาธารณะเรียกใช้ (พร็อกซีวัตถุวิธีเมธอดวัตถุ [] args) {ถ้า (realsubject == null) {realsubject = new realsubject (); } method.invoke (realsubject, args); กลับมาอีกครั้ง -ตัวอย่างรหัสลูกค้า
ไคลเอนต์ระดับสาธารณะ {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {หัวเรื่องหัวเรื่อง = (หัวเรื่อง) proxy.newinstance (classloader.getSystemloader (), realsubject.class.getInterfaces (), ใหม่ DynamicProxy (); Subject.sayhello (); subject.saygoodbye (); -ดังที่เห็นได้จากรหัสข้างต้นเราจำเป็นต้องใช้พร็อกซีแบบไดนามิกใน JDK ใช้ proxy.newinstance (classloader, อินเตอร์เฟส [], InvokeHandler) เพื่อสร้างคลาสพร็อกซีแบบไดนามิก วิธีการใหม่ของนิวอินซ์มีพารามิเตอร์สามตัวซึ่งเป็นตัวแทนของคลาสโหลดเดอร์รายการของอินเทอร์เฟซที่คุณต้องการให้คลาสพร็อกซีถูกนำไปใช้และอินสแตนซ์ที่ใช้อินเตอร์เฟส InvokeHandler พร็อกซีแบบไดนามิกส่งผ่านกระบวนการดำเนินการของแต่ละวิธีไปยังวิธีการเรียกใช้สำหรับการประมวลผล
JDK Dynamic Proxy ต้องการให้พร็อกซีต้องเป็นอินเทอร์เฟซ แต่คลาสที่เรียบง่ายไม่สามารถทำได้ คลาสพร็อกซีที่สร้างขึ้นโดย JDK Dynamic Proxy จะสืบทอดคลาสพร็อกซีและคลาสพร็อกซีจะใช้รายการอินเตอร์เฟสทั้งหมดที่คุณผ่านเข้ามาดังนั้นประเภทสามารถส่งไปยังประเภทอินเตอร์เฟส ด้านล่างนี้เป็นแผนภาพโครงสร้างของพร็อกซี
จะเห็นได้ว่าพร็อกซีเป็นวิธีการคงที่ทั้งหมดดังนั้นหากคลาสพร็อกซีไม่ได้ใช้อินเทอร์เฟซใด ๆ ก็เป็นประเภทพร็อกซีและไม่มีวิธีการอินสแตนซ์
แน่นอนถ้าคุณเข้าร่วมคุณต้องพร็อกซีคลาสที่ไม่ได้ใช้อินเทอร์เฟซที่แน่นอนและวิธีการของคลาสนี้จะเหมือนกับที่กำหนดโดยอินเทอร์เฟซอื่น ๆ และสามารถนำไปใช้ได้อย่างง่ายดายโดยใช้การสะท้อนกลับ
Public Class DynamicProxy ใช้ InvokeHandler {// ชั้นเรียนที่คุณต้องการพร็อกซีเป้าหมายส่วนตัว TargetClass TargetClass = NULL; // เริ่มต้นคลาสนี้ DynamicProxy (TargetClass TargetClass) {this.targetClass = TargetClass; } วัตถุสาธารณะเรียกใช้ (พร็อกซีวัตถุวิธีเมธอด, วัตถุ [] args) {// ใช้การสะท้อนกลับเพื่อรับคลาสที่คุณต้องการพร็อกซีเมธอด mymethod = targetClass.getClass (). getDeclaredMethod (method.getName () MyMethod.setAccessible (จริง); return mymethod.invoke (targetclass, args); -4. การวิเคราะห์รหัสแหล่งพร็อกซีแบบไดนามิก (JDK7)
หลังจากดูตัวอย่างข้างต้นเราเพิ่งรู้วิธีใช้พร็อกซีแบบไดนามิก อย่างไรก็ตามมันยังคงหมอกเกี่ยวกับวิธีการสร้างพร็อกซีคลาสที่เรียกว่าวิธีการเรียกใช้ ฯลฯ การวิเคราะห์ต่อไปนี้
1. วัตถุพร็อกซีถูกสร้างขึ้นอย่างไร?
ก่อนอื่นให้ดูที่ซอร์สโค้ดของพร็อกซีวิธีการใหม่:
วัตถุคงที่สาธารณะ newproxyinstance (classloader loader, คลาส <s?> [] อินเตอร์เฟส, InvocationHandler h) พ่น unledalargumentException {} // รับข้อมูลอินเตอร์เฟสคลาสสุดท้าย <?> [] intfs = interfaces.clone (); Final SecurityManager SM = System.getSecurityManager (); if (sm! = null) {checkproxyaccess (reftection.getCallerClass (), loader, intfs); } // สร้างคลาสพร็อกซีคลาส <?> cl = getproxyclass0 (โหลดเดอร์, intfs); // ... ตกลงมาดูครึ่งแรกก่อน}จากซอร์สโค้ดจะเห็นได้ว่าการสร้างคลาสพร็อกซีขึ้นอยู่กับวิธี GetProxyClass0 ถัดไปมาดูซอร์สโค้ด GetProxyClass0:
คลาสสแตติกส่วนตัว <?> getProxyClass0 (classloader loader, คลาส <?> ... อินเทอร์เฟซ) {// จำนวนรายการอินเตอร์เฟสต้องไม่เกิน 0xffff ถ้า (อินเทอร์เฟซ. } // หมายเหตุที่นี่คำอธิบายต่อไปนี้จะได้รับรายละเอียดเพื่อส่งคืน proxyclasscache.get (โหลดเดอร์อินเตอร์เฟส); - คำอธิบายของ proxyclasscache.get คือ: หากคลาสพร็อกซีที่ใช้รายการอินเทอร์เฟซมีอยู่แล้วให้ใช้โดยตรงจากแคช หากไม่มีอยู่จะมีการสร้างหนึ่งผ่าน ProxyclassFactory
ก่อนที่จะดูซอร์สโค้ดของ proxyclasscache.get ให้เข้าใจ ProxyClassCache สั้น ๆ :
private static final finalcache <classloader, class <?> [], คลาส <? >> proxyclasscache = new beakcache <> (keyfactory ใหม่ (), proxyclassfactory ใหม่ ());
ProxyClassCache เป็นแคชประเภทที่อ่อนแอ ตัวสร้างมีพารามิเตอร์สองตัว หนึ่งในนั้นคือ ProxyclassFactory ที่ใช้ในการสร้างคลาสพร็อกซี ต่อไปนี้เป็นซอร์สโค้ดของ proxyclasscache.get:
คลาสสุดท้ายของคลาส beakcache <k, p, v> {... สาธารณะ v get (k key, พารามิเตอร์ p) {}}ที่นี่ k หมายถึงคีย์, p หมายถึงพารามิเตอร์, v แทนค่า
สาธารณะ v รับ (k key, พารามิเตอร์ p) {// java7 วิธีการตัดสิน nullobject ถ้าพารามิเตอร์ว่างเปล่าข้อยกเว้นที่มีข้อความที่ระบุจะถูกโยนทิ้ง หากไม่ว่างเปล่าให้กลับมา Objects.requirenonnull (พารามิเตอร์); // ทำความสะอาดโครงสร้างข้อมูลของ WeakhashMap ที่มีการอ้างอิงที่อ่อนแอซึ่งโดยทั่วไปใช้ในการแคช expungestaleentries (); // รับ Cachekey จากคิวอ็อบเจ็กต์ Cachekey = cachekey.valueof (คีย์, refqueue); // เติมซัพพลายเออร์ด้วยการโหลดขี้เกียจ พร้อมกันเป็นแผนที่ที่ปลอดภัยจากเธรดพร้อมกัน <วัตถุซัพพลายเออร์ <v>> valuesmap = map.get (cachekey); if (valuesMap == null) {concurrentMap <วัตถุ, ซัพพลายเออร์ <v>> oldValuesMap = map.putifabsent (cachekey, valuemap = ใหม่พร้อมกันพร้อมกัน <> ()); if (oldValuesMap! = null) {valueMap = oldValUESMAP; }} // สร้าง subkey และดึงซัพพลายเออร์ที่เป็นไปได้ <v> เก็บไว้โดย // subkey จาก valuemap object subkey = objects.requirenonnull (subkeyfactory.apply (คีย์, พารามิเตอร์)); ซัพพลายเออร์ <v> ซัพพลายเออร์ = valuemap.get (subkey); โรงงานโรงงาน = null; ในขณะที่ (จริง) {ถ้า (ซัพพลายเออร์! = null) {// รับค่าจากซัพพลายเออร์ ค่านี้อาจเป็นโรงงานหรือการรับรู้แคช // สามประโยคต่อไปนี้เป็นรหัสหลักซึ่งส่งคืนคลาสที่ใช้งาน InvokeHandler และมีข้อมูลที่ต้องการ v value = supplier.get (); if (value! = null) {ค่าส่งคืน; }} // อื่น ๆ ไม่มีซัพพลายเออร์ในแคช // หรือซัพพลายเออร์ที่ส่งคืนค่า null (สามารถล้าง cachevalue // หรือโรงงานที่ไม่ประสบความสำเร็จในการติดตั้ง cachevalue) // กระบวนการต่อไปนี้เป็นกระบวนการของการเติมซัพพลายเออร์ถ้า (โรงงาน == null) {// สร้างโรงงาน} ฟังก์ชั่นของการวนรอบคือการรับคลาสที่ใช้อย่างต่อเนื่องที่ใช้ InvokeHandler คลาสนี้สามารถรับได้จากแคชหรือสร้างจาก ProxyFactoryClass
โรงงานเป็นคลาสภายในที่ใช้อินเตอร์เฟสซัพพลายเออร์ <v> คลาสนี้จะแทนที่วิธี GET และวิธีการอินสแตนซ์ของประเภท ProxyFactoryClass เรียกว่าในวิธี GET วิธีนี้เป็นวิธีที่แท้จริงในการสร้างคลาสพร็อกซี มาดูซอร์สโค้ดของ ProxyFactoryClass#ใช้วิธีการ:
คลาสสาธารณะ <?> ใช้ (classloader loader, คลาส <?> [] อินเตอร์เฟส) {แผนที่ <คลาส <?>, boolean> interfaceset = new identityHashMap <> (interfaces.length); สำหรับ (คลาส <?> intf: อินเตอร์เฟส) { /* ตรวจสอบว่าตัวโหลดคลาสแก้ไขชื่อของอินเทอร์เฟซนี้ไปยังวัตถุคลาสเดียวกัน* / คลาส <?> interfaceclass = null; ลอง {// โหลดข้อมูลเกี่ยวกับแต่ละ interfaceclass = class.forName (intf.getName (), false, loader); } catch (classnotFoundException e) {} // ถ้าคลาสที่โหลดด้วยคลาสโหลดของคุณเองไม่เท่ากับคลาสที่คุณผ่านเข้ามาให้โยนข้อยกเว้นถ้า (interfaceclass! = intf) {โยน unlegalargumentException ใหม่ (intf + "ไม่สามารถมองเห็นได้จากคลาส loader"); } // ถ้าขาเข้าไม่ใช่ประเภทอินเตอร์เฟสถ้า (! interfaceclass.isinterface ()) {โยน unleglargumentException ใหม่ (interfaceclass.getName () + "ไม่ใช่อินเทอร์เฟซ"); } // ตรวจสอบว่าอินเทอร์เฟซทำซ้ำหรือไม่ถ้า (interfaceset.put (interfaceclass, boolean.true)! = null) {โยน unleglArgumentException ใหม่ ("อินเทอร์เฟซซ้ำ:" + interfaceclass.getName ()); }} สตริง proxypkg = null; // แพ็คเกจเพื่อกำหนดคลาสพร็อกซีใน /* บันทึกแพ็คเกจของอินเทอร์เฟซพร็อกซีที่ไม่ใช่สาธารณะเพื่อให้คลาสพร็อกซีจะถูกกำหนดในแพ็คเกจเดียวกัน * ตรวจสอบว่าอินเทอร์เฟซพร็อกซีที่ไม่ใช่สาธารณะทั้งหมดอยู่ในแพ็คเกจเดียวกัน */// ย่อหน้านี้ขึ้นอยู่กับว่ามีอินเทอร์เฟซที่ไม่เปิดเผยต่อสาธารณะในอินเทอร์เฟซที่คุณผ่านถ้าเป็นเช่นนั้นอินเทอร์เฟซทั้งหมดเหล่านี้จะต้องกำหนดไว้ในแพ็คเกจเดียว มิฉะนั้นให้โยนข้อยกเว้นสำหรับ (คลาส <?> intf: อินเตอร์เฟส) {int flags = intf.getModifiers (); if (! modifier.ispublic (แฟล็ก)) {ชื่อสตริง = intf.getName (); int n = name.lastindexof ('.'); สตริง pkg = ((n == -1)? "": name.substring (0, n + 1)); if (proxypkg == null) {proxypkg = pkg; } else if (! pkg.equals (proxypkg)) {โยน unlegalargumentException ใหม่ ("อินเทอร์เฟซที่ไม่ใช่แบบสาธารณะจากแพ็คเกจที่แตกต่างกัน"); }}}} if (proxypkg == null) {// ถ้าไม่มีอินเทอร์เฟซพร็อกซีที่ไม่ใช่สาธารณะใช้ com.sun.proxy แพ็คเกจ proxypkg = rechormutil.proxy_package + "."; } / * * เลือกชื่อสำหรับคลาสพร็อกซีเพื่อสร้าง */ long num = nextuniquenumber.getandincrement (); // สร้างชื่อคลาสของคลาสพร็อกซีแบบสุ่ม, $ proxy + num string proxyname = proxypkg + proxyclassnameprefix + num; /** สร้างไฟล์คลาสของคลาสพร็อกซีส่งคืนไบต์สตรีม*/ ไบต์ [] proxyclassFile = proxygenerator.generateProxyclass (proxyname, อินเตอร์เฟส); ลอง {return defeleclass0 (loader, proxyname, proxyclassfile, 0, proxyclassfile.length); } catch (classformaterror e) {// สิ้นสุดโยน unlegalargumentException ใหม่ (e.toString ()); -ProxyFactoryClass#ดังกล่าวข้างต้นใช้เป็นวิธีการสร้างคลาสพร็อกซีซึ่งไม่ถูกต้องจริง ๆ หลังจากอ่านซอร์สโค้ดที่นี่เราจะพบว่า proxygenerator#generateProxyclass เป็นวิธีการสร้างคลาสพร็อกซีอย่างแท้จริง สร้างไฟล์คลาสที่สอดคล้องกันตามองค์ประกอบของคลาส Java Bytecode (ดูบทความอื่น ๆ ของฉัน Java bytecode การเรียนรู้หมายเหตุ) ซอร์สโค้ดเฉพาะของ proxygenerator#generateProxyClass มีดังนี้:
ไบต์ส่วนตัว [] GenerateclassFile () { / * * ขั้นตอนที่ 1: ประกอบวัตถุ Proxymethod สำหรับวิธีการทั้งหมดเพื่อ * สร้างรหัสการส่งพร็อกซีสำหรับ */ // วิธีการ AddProxyMethod คือการเพิ่มวิธีการทั้งหมดในรายการและสอดคล้องกับคลาสที่สอดคล้องกัน // นี่คือสามวิธีที่สอดคล้องกับวัตถุ ToString และ Equals AddProxyMethod (HashCodeMethod, Object.class); addproxymethod (Equalsmethod, Object.class); addproxymethod (ToStringMethod, Object.class); // เปรียบเทียบอินเทอร์เฟซในรายการอินเตอร์เฟสด้วยวิธีการภายใต้อินเตอร์เฟสสำหรับ (int i = 0; i <interfaces.length; i ++) {method [] methods = interfaces [i] .getMethods (); สำหรับ (int j = 0; j <methods.length; j ++) {addproxymethod (วิธีการ [j], อินเตอร์เฟส [i]); }} / * * สำหรับแต่ละชุดของวิธีพร็อกซีที่มีลายเซ็นเดียวกัน * ตรวจสอบว่าประเภทการส่งคืนของวิธีการนั้นเข้ากันได้ */ สำหรับ (รายการ <proxymethod> signmethods: proxymethods.values ()) {checkreturntypes (sigmethods); } / * * ขั้นตอนที่ 2: ประกอบ FieldInfo และ MethodInfo structs สำหรับฟิลด์ * ทั้งหมดและวิธีการทั้งหมดในชั้นเรียนที่เรากำลังสร้าง */// เพิ่มวิธีการสร้างเข้ากับวิธีการซึ่งเป็นเพียงตัวสร้างเพียงตัวเดียวซึ่งเป็นตัวสร้างที่มีอินเตอร์เฟส InvocationHandler.//this เป็นวิธีจริงในการเพิ่มวิธีการในไฟล์คลาสนั่นคือคลาสพร็อกซี อย่างไรก็ตามยังไม่ได้ดำเนินการ มันถูกเพิ่มเข้ามาก่อนและรอลูป คำอธิบายชื่อของตัวสร้างในไฟล์คลาสคือ <init> ลอง {methods.add (generateconstructor ()); สำหรับ (รายการ <proxymethod> signMethods: proxymethods.values ()) {สำหรับ (proxymethod pm: signmethods) {// เพิ่มแอตทริบิวต์ประเภทวิธีการในแต่ละวิธีพร็อกซี หมายเลข 10 เป็นตัวระบุของไฟล์คลาสซึ่งหมายความว่าแอตทริบิวต์เหล่านี้เป็นฟิลด์ ADD (FieldInfo ใหม่ (PM.MethodfieldName, "LJAVA/LANG/Reflect/Method;", ACC_PRIVATE | ACC_STATIC)); // เพิ่มวิธีพร็อกซีแต่ละวิธีลงในวิธีการคลาสพร็อกซี ADD (PM.GenerateMethod ()); }} // เพิ่มบล็อกการเริ่มต้นแบบคงที่และเริ่มต้นแต่ละแอตทริบิวต์ ที่นี่บล็อกรหัสคงที่เรียกว่าตัวสร้างคลาส จริงๆแล้วมันเป็นวิธีที่มีชื่อ <linit> ดังนั้นเพิ่มลงในวิธีการรายการวิธี ADD (GeneratestaticInitializer ()); } catch (ioexception e) {โยน new InternalError ("ข้อยกเว้น I/O ที่ไม่คาดคิด"); } // จำนวนวิธีและแอตทริบิวต์ต้องไม่เกิน 65535 รวมถึงจำนวนอินเทอร์เฟซก่อนหน้า // นี่เป็นเพราะในไฟล์คลาสตัวเลขเหล่านี้จะแสดงใน hexadecimal 4 บิตดังนั้นค่าสูงสุดคือ 2 ถึงพลังของ 16 -1 ถ้า (methods.size ()> 65535) {โยน unlegalargumentException ใหม่ ("จำกัด วิธีการ"); } if (fields.size ()> 65535) {โยน unlegalargumentException ใหม่ ("เกินขีด จำกัด ฟิลด์เกิน"); } // ขั้นตอนต่อไปคือการเขียนไฟล์คลาสรวมถึงหมายเลขเวทย์มนตร์ชื่อคลาสพูลคงที่และชุดข้อมูลอื่น ๆ ของไบต์ ฉันจะไม่เข้าไปดูรายละเอียด หากคุณต้องการคุณสามารถอ้างถึงความรู้ที่เกี่ยวข้องของ JVM Virtual Machine Bytecode cp.getClass (dottoslash (classname)); cp.getClass (superclassname); สำหรับ (int i = 0; i <interfaces.length; i ++) {cp.getClass (dottoslash (อินเตอร์เฟส [i] .getName ())); } cp.setReadOnly (); ByTeArrayOutputStream Bout = New ByteArrayOutputStream (); dataOrtputStream dout = ใหม่ dataOrtputStream (การแข่งขัน); ลอง {// U4 Magic; dout.writeint (0xcafebabe); // u2 minor_version; dout.writeshort (classfile_minor_version); // u2 major_version; dout.writeshort (classfile_major_version); cp.write (dout); // (เขียนพูลคงที่) // u2 access_flags; dout.writeshort (acc_public | acc_final | acc_super); // u2 this_class; dout.writeshort (cp.getclass (dottoslash (classname))); // u2 super_class; dout.writeshort (cp.getclass (superclassname)); // u2 interfaces_count; dout.writeshort (interfaces.length); // U2 อินเตอร์เฟส [interfaces_count]; สำหรับ (int i = 0; i <interfaces.length; i ++) {dout.writeshort (cp.getClass (dottoslash (อินเตอร์เฟส [i] .getName ()))); } // u2 fields_count; dout.writeshort (fields.size ()); // field_info ฟิลด์ [fields_count]; สำหรับ (fieldInfo f: ฟิลด์) {f.write (dout); } // u2 methods_count; dout.writeshort (methods.size ()); // method_info วิธีการ [methods_count]; สำหรับ (methodInfo m: วิธีการ) {m.write (dout); } // u2 attributes_count; dout.writeshort (0); // (ไม่มีแอตทริบิวต์ ClassFile สำหรับคลาสพร็อกซี)} catch (ioexception e) {โยนใหม่ภายใน ("ข้อยกเว้น I/O ที่ไม่คาดคิด"); } return bout.tobytearray (); -หลังจากการโทรเลเยอร์คลาสพร็อกซีจะถูกสร้างขึ้นในที่สุด
2. ใครเรียกว่า Invoke?
เราจำลอง JDK เพื่อสร้างคลาสพร็อกซีด้วยตัวเองด้วยชื่อคลาส TestProxygen:
Public Class TestGeneratorProxy {โมฆะสาธารณะคงที่หลัก (สตริง [] args) พ่น ioexception {byte [] classfile = proxygenerator.generateproxyclass ("testproxygen", subject.class.getinterfaces ()); ไฟล์ไฟล์ = ไฟล์ใหม่ ("/ผู้ใช้/yadoao/เดสก์ท็อป/testproxygen.class"); fileOutputStream fos = new fileOutputStream (ไฟล์); fos.write (classfile); fos.flush (); fos.close (); -ถอดรหัสไฟล์คลาสด้วย JD-GUI และผลลัพธ์มีดังนี้:
นำเข้า com.su.dynamicproxy.isubject; นำเข้า java.lang.reflect.invocationhandler; นำเข้า java.lang.reflect.method; นำเข้า java.lang.reflect.proxy; นำเข้า java.lang.reflect.undeclaredthrowableException วิธีการคงที่ส่วนตัว M1; วิธีการคงที่ส่วนตัว M0; วิธีการคงที่ส่วนตัว M4; วิธีการคงที่ส่วนตัว M2; Public TestProxygen (InvocationHandler ParaminVocationHandler) พ่น {super (paraminVocationHandler); } เป็นโมฆะสุดท้ายสาธารณะ sayshello () พ่น {ลอง {this.h.invoke (นี่, m3, null); กลับ; } catch (ข้อผิดพลาด | runtimeException localerror) {โยน localError; } catch (localthrowable localable) {โยน undeclaredthrowableexception ใหม่ (localthrowable); }} บูลีนสุดท้ายสาธารณะเท่ากับ (วัตถุ paramobject) พ่น {ลอง {return ((บูลีน) this.h.invoke (นี่, m1, วัตถุใหม่ [] {paramobject})). booleanValue (); } catch (ข้อผิดพลาด | runtimeException localerror) {โยน localError; } catch (localthrowable localable) {โยน undeclaredthrowableexception ใหม่ (localthrowable); }} สาธารณะ int สุดท้าย hashCode () พ่น {ลอง {return ((จำนวนเต็ม) this.h.invoke (นี่, m0, null)). intvalue (); } catch (ข้อผิดพลาด | runtimeException localerror) {โยน localError; } catch (localthrowable localable) {โยน undeclaredthrowableexception ใหม่ (localthrowable); }} โมฆะสุดท้ายสาธารณะ saysgoodbye () พ่น {ลอง {this.h.invoke (นี่, m4, null); กลับ; } catch (ข้อผิดพลาด | runtimeException localerror) {โยน localError; } catch (localthrowable localable) {โยน undeclaredthrowableexception ใหม่ (localthrowable); }} Public Final String toString () พ่น {ลอง {return (string) this.h.invoke (นี่, m2, null); } catch (ข้อผิดพลาด | runtimeException localerror) {โยน localError; } catch (localthrowable localable) {โยน undeclaredthrowableexception ใหม่ (localthrowable); }} คงที่ {ลอง {m3 = class.forName ("com.su.dynamicproxy.isubject"). getMethod ("Sayhello", คลาสใหม่ [0]); m1 = class.forname ("java.lang.Object"). getMethod ("เท่ากับ", คลาสใหม่ [] {class.forname ("java.lang.Object")}); m0 = class.forname ("java.lang.Object"). getMethod ("hashCode", คลาสใหม่ [0]); m4 = class.forname ("com.su.dynamicproxy.isubject"). getMethod ("saygoodbye", คลาสใหม่ [0]); m2 = class.forname ("java.lang.Object"). getMethod ("toString", คลาสใหม่ [0]); กลับ; } catch (nosuchmethodexception localnosuchmethodexception) {โยน nosuchmethoderror ใหม่ (localnosuchmethodexception.getMessage ()); } catch (classnotfoundexception localclassnotfoundexception) {โยน noclassdeffounderror ใหม่ (localclassnotfoundexception.getMessage ()); - ก่อนอื่นฉันสังเกตเห็นว่าตัวสร้างของคลาสพร็อกซีที่สร้างขึ้นถูกส่งผ่านในคลาสที่ใช้อินเตอร์เฟส InvokeHandler เป็นพารามิเตอร์และเรียกว่าตัวสร้างของพร็อกซีคลาสแม่
ฉันสังเกตเห็นบล็อกการเริ่มต้นแบบคงที่หลายครั้งอีกครั้ง บล็อกการเริ่มต้นแบบสแตติกที่นี่คือการเริ่มต้นรายการอินเตอร์เฟสพร็อกซีและวิธีการ HashCode, ToString และวิธีการเท่ากับ
ในที่สุดก็มีกระบวนการโทรของวิธีการเหล่านี้ซึ่งทั้งหมดเป็นการเรียกกลับไปยังวิธีการเรียกใช้
จบลงด้วยการวิเคราะห์รูปแบบพร็อกซีนี้