ผ่านการวิเคราะห์บทความก่อนหน้านี้เรารู้ว่าคลาสพร็อกซีถูกสร้างขึ้นผ่านโรงงาน ProxyclassFactory ของพร็อกซีคลาส คลาสโรงงานนี้จะเรียกเมธอด generateProxyClass () ของคลาส proxygenerator เพื่อสร้าง bytecode ของคลาสพร็อกซี คลาส Proxygenerator ถูกเก็บไว้ในแพ็คเกจ Sun.misc เราสามารถค้นหาคลาสนี้ผ่านซอร์สโค้ด OpenJDK เนื้อหาหลักของวิธีการคงที่ proxyclass () ของคลาสนี้คือการเรียกใช้วิธีอินสแตนซ์ที่สร้าง classFile () เพื่อสร้างไฟล์คลาส ลองมาดูสิ่งที่ทำภายในวิธี GenerateclassFile ()
ไบต์ส่วนตัว [] generateclassFile () {// ขั้นตอนแรกคือการรวบรวมวิธีการทั้งหมดลงในวัตถุ proxymethod // วิธีแรกสร้างวิธีการพร็อกซีเช่น toString, hashcode, เท่ากับ ฯลฯ addproxymethod addproxymethod (Equalsmethod, Object.class); addproxymethod (ToStringMethod, Object.class); // ถ่ายโอนแต่ละวิธีของแต่ละอินเตอร์เฟสและสร้างวัตถุ proxymethod สำหรับมันสำหรับ (int i = 0; i <interfaces.length; i ++) {method [] methods = interfaces [i] .getMethods (); สำหรับ (int j = 0; j <methods.length; j ++) {addproxymethod (วิธีการ [j], อินเตอร์เฟส [i]); }} // สำหรับวิธีพร็อกซีที่มีลายเซ็นเดียวกันตรวจสอบว่าค่าการส่งคืนของวิธีนี้เข้ากันได้กับ (รายการ <signatures> signatures: proxymethods.values ()) {checkreturntypes (sigmethods); } // ขั้นตอนที่ 2 รวบรวมข้อมูลฟิลด์และข้อมูลวิธีการทั้งหมดของไฟล์คลาสที่จะสร้างลอง {// เพิ่มวิธีการสร้างวิธีการ ADD (GenerAteconstructor ()); // โอนวิธีพร็อกซีในแคชสำหรับ (รายการ <proxymethod> signmethods: proxymethods.values ()) {สำหรับ (proxymethod pm: signmethods) {// เพิ่มเขตข้อมูลคงที่ของคลาสพร็อกซี Fields.add (FieldInfo ใหม่ (PM.MethodfieldName, "Ljava/Lang/Reflect/Method;", acc_private | acc_static)); // เพิ่มวิธีการพร็อกซีของวิธีพร็อกซีคลาส ADD (PM.GenerateMethod ()); }} // เพิ่มวิธีการเริ่มต้นฟิลด์แบบคงที่วิธีการ ADD (generatestaticInitializer ()); } catch (ioexception e) {โยน new InternalError ("ข้อยกเว้น I/O ที่ไม่คาดคิด"); } // วิธีการตรวจสอบและการรวบรวมฟิลด์ไม่เกิน 65535 ถ้า (methods.size ()> 65535) {โยน unlegalargumentException ใหม่ ("เกินขีด จำกัด วิธีการ"); } if (fields.size ()> 65535) {โยน unlegalargumentException ใหม่ ("เกินขีด จำกัด ฟิลด์เกิน"); } if (fields.size ()> 65535) {โยน unlegalargumentException ใหม่ ("เกินขีด จำกัด ฟิลด์เกิน"); } // ขั้นตอนที่ 3 เขียนไปยังไฟล์คลาสสุดท้าย // ตรวจสอบว่ามีชื่อที่ผ่านการรับรองของคลาสพร็อกซีในพูลคงที่ cp.getClass (dottoslash (className)); // ตรวจสอบว่ามีชื่อที่มีคุณสมบัติครบถ้วนของคลาสพาเรนต์พร็อกซีคลาสในพูลคงที่และชื่อคลาสพาเรนต์คือ: "Java/Lang/Reflect/Proxy" cp.getClass (SuperClassName); // ตรวจสอบว่าชื่อที่ผ่านการรับรองเต็มรูปแบบของอินเทอร์เฟซคลาสพร็อกซีสำหรับ (int i = 0; i <interfaces.length; i ++) {cp.getClass (dottoslash (อินเตอร์เฟส [i] .getName ())); } // ถัดไปเพื่อเริ่มเขียนไฟล์ตั้งค่าพูลคงที่เพื่ออ่านเฉพาะ cp.setReadonly (); ByTeArrayOutputStream Bout = New ByteArrayOutputStream (); dataOrtputStream dout = ใหม่ dataOrtputStream (การแข่งขัน); ลอง {// 1 เขียนถึงหมายเลขวิเศษ dout.writeInt (0xcafebabe); // 2. เขียนไปยังหมายเลขรุ่นรอง dout.writeshort (classfile_minor_version); // 3. เขียนไปยังหมายเลขเวอร์ชันหลัก dout.writeshort (classfile_major_version); // 4. เขียนไปยังสระว่ายน้ำคงที่ cp.write (dout); // 5. เขียนการเข้าถึงตัวปรับเปลี่ยน dout.writeshort (acc_public | acc_final | acc_super); // 6. เขียนดัชนีคลาส dout.writeshort (cp.getclass (dottoslash (classname))); // 7. การเขียนดัชนีคลาสพาเรนต์คลาสพร็อกซีที่สร้างขึ้นมาจากพร็อกซี dout.writeshort (cp.getClass (superclassName)); // 8. การเขียนค่าจำนวนอินเตอร์เฟส dout.writeshort (interfaces.length); // 9. ชุดอินเตอร์เฟสเขียนสำหรับ (int i = 0; i <interfaces.length; i ++) {dout.writeshort (cp.getClass (dottoslash (อินเตอร์เฟส [i] .getName ()))); } // 10 การเขียนจำนวนฟิลด์ค่า dout.writeshort (fields.size ()); // 11. คอลเลกชันฟิลด์เขียนสำหรับ (FieldInfo F: ฟิลด์) {f.write (dout); } // 12 วิธีการเขียนจำนวนค่า dout.writeshort (methods.size ()); // 13. การรวบรวมวิธีการเขียนสำหรับ (methodInfo m: วิธีการ) {m.write (dout); } // 14 ค่าการนับจำนวนคุณสมบัติการนับไฟล์พร็อกซีไม่มีแอตทริบิวต์ดังนั้นจึงเป็น 0 dout.writeshort (0); } catch (ioexception e) {โยน new InternalError ("ข้อยกเว้น I/O ที่ไม่คาดคิด"); } // แปลงเป็นอาร์เรย์ไบนารีเป็นเอาต์พุตส่งคืน bout.tobytearray ();}คุณจะเห็นได้ว่าวิธี GenerateclassFile () นั้นถูกเชื่อมต่อแบบไดนามิกตามโครงสร้างไฟล์คลาส ไฟล์คลาสคืออะไร? ที่นี่เราจะอธิบายก่อนว่าไฟล์ Java ที่เรามักจะเขียนจบลงด้วย. java หลังจากเขียนแล้วให้รวบรวมผ่านคอมไพเลอร์และสร้างไฟล์. class ไฟล์. class นี้เป็นไฟล์คลาส การดำเนินการของโปรแกรม Java นั้นขึ้นอยู่กับไฟล์คลาสเท่านั้นและไม่มีส่วนเกี่ยวข้องกับไฟล์ Java ไฟล์คลาสนี้อธิบายข้อมูลของคลาส เมื่อเราต้องการใช้คลาสเครื่องเสมือน Java จะโหลดไฟล์คลาสของคลาสนี้ล่วงหน้าและดำเนินการเริ่มต้นและการตรวจสอบที่เกี่ยวข้อง เครื่องเสมือน Java สามารถมั่นใจได้ว่างานเหล่านี้จะเสร็จสมบูรณ์ก่อนที่คุณจะใช้คลาสนี้ เราแค่ต้องใช้มันด้วยความอุ่นใจโดยไม่สนใจว่าเครื่องเสมือนของ Java จะโหลดอย่างไร แน่นอนว่าไฟล์คลาสไม่จำเป็นต้องรวบรวมโดยการรวบรวมไฟล์ Java คุณสามารถเขียนไฟล์คลาสได้โดยตรงผ่านตัวแก้ไขข้อความ ที่นี่พร็อกซีไดนามิก JDK สร้างไฟล์คลาสแบบไดนามิกผ่านโปรแกรม กลับไปที่รหัสด้านบนอีกครั้งและดูว่าการสร้างไฟล์คลาสส่วนใหญ่แบ่งออกเป็นสามขั้นตอน:
ขั้นตอนที่ 1: รวบรวมวิธีการพร็อกซีทั้งหมดที่จะสร้างให้ห่อไว้ในวัตถุพร็อกซิมเอทและลงทะเบียนลงในคอลเลกชันแผนที่
ขั้นตอนที่ 2: รวบรวมข้อมูลฟิลด์และข้อมูลวิธีการทั้งหมดที่จะสร้างขึ้นสำหรับไฟล์คลาส
ขั้นตอนที่ 3: หลังจากทำงานด้านบนเสร็จแล้วให้เริ่มประกอบไฟล์คลาส
เรารู้ว่าส่วนหลักของคลาสคือฟิลด์และวิธีการของมัน มามุ่งเน้นไปที่ขั้นตอนที่สองเพื่อดูว่าฟิลด์และวิธีการใดที่สร้างขึ้นสำหรับคลาสพร็อกซี ในขั้นตอนที่สองสี่สิ่งต่อไปนี้ทำตามลำดับ
1. สร้างตัวสร้างพารามิเตอร์สำหรับคลาสพร็อกซีส่งผ่านในการอ้างอิงไปยังอินสแตนซ์ InvocationHandler และเรียกตัวสร้างพารามิเตอร์ของคลาสแม่
2. วนซ้ำผ่านการรวบรวมแผนที่ของวิธีพร็อกซีสร้างวิธีการที่สอดคล้องกันประเภทโดเมนคงที่สำหรับแต่ละวิธีพร็อกซีและเพิ่มลงในคอลเลกชันฟิลด์
3. วนซ้ำผ่านการรวบรวมแผนที่ของวิธีพร็อกซีสร้างวัตถุวิธีการที่สอดคล้องกันสำหรับแต่ละวิธีพร็อกซีและเพิ่มลงในการรวบรวมวิธีการ
4. สร้างวิธีการเริ่มต้นแบบคงที่สำหรับคลาสพร็อกซี วิธีการเริ่มต้นแบบคงที่นี้ส่วนใหญ่กำหนดการอ้างอิงของแต่ละวิธีพร็อกซีให้กับฟิลด์คงที่ที่สอดคล้องกัน
ผ่านการวิเคราะห์ข้างต้นเราสามารถรู้ได้อย่างคร่าวๆว่า JDK Dynamic Proxy ในที่สุดจะสร้างคลาสพร็อกซีที่มีโครงสร้างต่อไปนี้สำหรับเรา:
Proxy0 ระดับสาธารณะขยายพร็อกซีดำเนินการ UserDao {// ขั้นตอนที่ 1, สร้าง Proxy0 ที่ได้รับการป้องกันตัวสร้าง (InvocationHandler H) {super (h); } // ขั้นตอนที่ 2 สร้างโดเมนแบบคงที่แบบคงที่วิธีการคงที่ M1; // วิธี HashCode วิธีการคงที่ส่วนตัว M2; // วิธีการเท่ากับวิธีการคงที่ส่วนตัว M3; // วิธีการ ToString วิธีการคงที่ส่วนตัว M4; // ... // ขั้นตอนที่ 3, สร้างเมธอดพร็อกซี @Override สาธารณะ int hashCode () {ลอง {return (int) h.invoke (นี่, m1, null); } catch (throwable e) {โยน undeclaredthrowableException ใหม่ (E); }} @Override บูลีนสาธารณะเท่ากับ (Object obj) {ลอง {object [] args = วัตถุใหม่ [] {obj}; return (บูลีน) H.Invoke (นี่, m2, args); } catch (throwable e) {โยน undeclaredthrowableException ใหม่ (E); }} @Override สตริงสาธารณะ toString () {ลอง {return (string) h.invoke (นี่, m3, null); } catch (throwable e) {โยน undeclaredthrowableException ใหม่ (E); }} @Override โมฆะสาธารณะบันทึก (ผู้ใช้ผู้ใช้) {ลอง {// สร้างอาร์เรย์พารามิเตอร์หากมีการเพิ่มพารามิเตอร์หลายรายการในภายหลังเพียงแค่วัตถุ [] args = วัตถุใหม่ [] {ผู้ใช้}; H.Invoke (นี่, M4, Args); } catch (throwable e) {โยน undeclaredthrowableException ใหม่ (E); }} // ขั้นตอนที่ 4, สร้างวิธีการเริ่มต้นแบบคงที่แบบคงที่ {ลอง {class c1 = class.forName (object.class.getName ()); คลาส c2 = class.forname (userdao.class.getName ()); m1 = c1.getMethod ("hashcode", null); m2 = c1.getMethod ("เท่ากับ", คลาสใหม่ [] {object.class}); m3 = c1.getMethod ("toString", null); m4 = c2.getMethod ("บันทึก", คลาสใหม่ [] {user.class}); // ... } catch (exception e) {e.printstacktrace (); -เมื่อมาถึงจุดนี้หลังจากการวิเคราะห์แบบเลเยอร์และการสำรวจเชิงลึกของซอร์สโค้ด JDK เราได้กู้คืนรูปลักษณ์ดั้งเดิมของพร็อกซีคลาสที่สร้างขึ้นแบบไดนามิกและคำถามก่อนหน้านี้บางคำถามก็อธิบายได้ดีเช่นกัน
1. คลาสพร็อกซีสืบทอดคลาส Porxy ตามค่าเริ่มต้น เนื่องจาก Java รองรับการสืบทอดเดี่ยวเท่านั้น JDK Dynamic Proxy สามารถใช้อินเทอร์เฟซเท่านั้น
2. วิธีพร็อกซีจะเรียกวิธีการเรียกใช้ () ของ InvocationHandler ดังนั้นเราจำเป็นต้องเขียนเมธอด Invoke () ของ InvocationHandler ใหม่
3. เมื่อเรียกใช้เมธอด Invoke () อินสแตนซ์พร็อกซีเองวิธีการเป้าหมายและพารามิเตอร์วิธีการเป้าหมายจะถูกส่งผ่านอธิบายว่าพารามิเตอร์ของวิธีการเรียกใช้ () มาจาก
ใช้พร็อกซีที่สร้างขึ้นใหม่เป็นคลาสพร็อกซีเพื่อทดสอบอีกครั้งและคุณจะเห็นว่าผลลัพธ์สุดท้ายนั้นเหมือนกับคลาสพร็อกซีที่สร้างขึ้นแบบไดนามิกโดยใช้ JDK อีกครั้งการวิเคราะห์ของเรามีความน่าเชื่อถือและแม่นยำ เมื่อมาถึงจุดนี้บทความซีรีย์พร็อกซีของ JDK Dynamic ได้ประกาศให้สิ้นสุด จากการวิเคราะห์ซีรีส์นี้ผู้เขียนได้แก้ไขข้อสงสัยอันยาวนานในใจของเขาและฉันเชื่อว่าความเข้าใจของผู้อ่านเกี่ยวกับพร็อกซีไดนามิกของ JDK ได้กลายเป็นอีกก้าวหนึ่ง อย่างไรก็ตามความรู้บนกระดาษมักจะตื้น หากคุณต้องการที่จะดีกว่าเทคโนโลยีพร็อกซีไดนามิก JDK Dynamic ผู้อ่านสามารถอ้างถึงบทความชุดนี้เพื่อตรวจสอบซอร์สโค้ด JDK ด้วยตัวเองหรือแลกเปลี่ยนประสบการณ์การเรียนรู้กับผู้เขียนชี้ให้เห็นการวิเคราะห์ที่ไม่เหมาะสมของผู้เขียนเรียนรู้ร่วมกันและก้าวหน้าร่วมกัน
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น