คำอธิบายโดยละเอียดของ JDK Dynamic Proxy
บทความนี้ส่วนใหญ่แนะนำหลักการพื้นฐานของพร็อกซีไดนามิกของ JDK เพื่อให้ทุกคนสามารถเข้าใจพร็อกซี JDK อย่างลึกซึ้งยิ่งขึ้นรู้ว่ามันคืออะไร ทำความเข้าใจกับหลักการที่แท้จริงของพร็อกซีไดนามิก JDK และกระบวนการสร้าง ในอนาคตเมื่อเราเขียนพร็อกซี JDK เราสามารถเขียนพร็อกซีที่สมบูรณ์แบบโดยไม่ต้องตรวจสอบการสาธิต ก่อนอื่นมาสาธิตง่ายๆ กระบวนการวิเคราะห์ที่ตามมาขึ้นอยู่กับการสาธิตนี้เพื่อแนะนำ ตัวอย่างใช้งานโดยใช้ JDK 1.8
JDK พร็อกซี HelloWorld
แพ็คเกจ com.yo.proxy;/** * สร้างโดย Robin */Interface Public HelloWorld {Void Sayshello ();} แพ็คเกจ com.yao.proxy; นำเข้า com.yo.helloworld;/** * สร้างโดย Robin */คลาสสาธารณะ HelloWorldimpl ใช้ HelloWorld {โมฆะสาธารณะ Sayhello () {System.out.print ("Hello World"); - แพ็คเกจ com.yo.proxy; นำเข้า java.lang.reflect.invocationhandler; นำเข้า java.lang.reflect.method;/** * สร้างโดย Robin */คลาสสาธารณะ Myinvocationhandler ใช้ InvationHandler {เป้าหมายวัตถุส่วนตัว; สาธารณะ myinvocationhandler (เป้าหมายวัตถุ) {this.target = เป้าหมาย; } วัตถุสาธารณะเรียกใช้ (พร็อกซีวัตถุวิธีเมธอด, วัตถุ [] args) โยน {system.out.println ("วิธี:"+ method.getName ()+ "ถูกเรียกใช้!"); return method.invoke (เป้าหมาย, args); - แพ็คเกจ com.yao.proxy; นำเข้า com.yao.helloworld; นำเข้า java.lang.reflect.constructor; นำเข้า java.lang.reflect.invocationhandler; นำเข้า java.lang.reflect.invocationTargetException; โมฆะคงที่หลัก (String [] args) พ่น nosuchmethodexception, ungleclaccessexception, ravecocationTargetException, instantiationException {// มีสองวิธีในการเขียนที่นี่ เราใช้วิธีการเขียนที่ซับซ้อนเล็กน้อยซึ่งจะช่วยให้ทุกคนเข้าใจมากขึ้น คลาส <?> proxyclass = proxy.getProxyClass (jdkproxytest.class.getClassloader (), helloWorld.class); ตัวสร้างสุดท้าย <?> cons = proxyclass.getConstructor (InvocationHandler.class); สุดท้าย InvocationHandler IH = ใหม่ myinvocationhandler (ใหม่ helloWorldimpl ()); HelloWorld HelloWorld = (HelloWorld) ข้อเสีย NewInstance (IH); HelloWorld.sayhello (); // ต่อไปนี้เป็นวิธีการเขียนที่ง่ายกว่าโดยพื้นฐานแล้วข้างต้น/* HelloWorld HelloWorld = (HelloWorld) พร็อกซี newproxyinstance (jdkproxytest.class.getclassloader (), คลาสใหม่ <?> [] {helloWorld.class}, myinvocationhandler ใหม่ (ใหม่ helloWorldimpl ())); HelloWorld.sayhello (); -เรียกใช้รหัสด้านบนและพร็อกซี JDK แบบง่าย ๆ จะถูกนำไปใช้
กระบวนการสร้างตัวแทน
เหตุผลที่เราเรียก JDK Dynamic Proxy ทุกวันก็คือคลาสพร็อกซีนี้สร้างขึ้นโดย JDK แบบไดนามิกสำหรับเราที่รันไทม์ ก่อนที่จะอธิบายกระบวนการสร้างพร็อกซีก่อนอื่นเราจะเพิ่มพารามิเตอร์ -dsun.misc.proxygenerator.saveGeneratedFiles = จริงกับพารามิเตอร์การเริ่มต้น JVM ฟังก์ชั่นของมันคือการช่วยให้เราบันทึก bytecode ของคลาสพร็อกซีที่สร้างขึ้นโดย JDK ไปยังฮาร์ดดิสก์และช่วยให้เราดูเนื้อหาเฉพาะของพร็อกซีที่สร้างขึ้น ฉันใช้ ความคิด Intellij และหลังจากที่คลาสพร็อกซีถูกสร้างขึ้นมันถูกวางไว้โดยตรงในไดเรกทอรีรากของโครงการ โดยมีชื่อแพ็คเกจเฉพาะเป็นโครงสร้างไดเรกทอรี
กระบวนการสร้างคลาสพร็อกซีส่วนใหญ่มีสองส่วน:
รายการวิธี getProxyClass ของคลาสพร็อกซี: คุณต้องผ่านในคลาสโหลดเดอร์และอินเทอร์เฟซ
จากนั้นเรียกใช้วิธี getProxyClass0 และคำอธิบายประกอบในนั้นอธิบายไว้อย่างชัดเจน หากคลาสพร็อกซีที่ใช้อินเทอร์เฟซปัจจุบันมีอยู่มันจะถูกส่งคืนโดยตรงจากแคช หากไม่มีอยู่มันจะถูกสร้างขึ้นผ่าน ProxyclassFactory สามารถเห็นได้อย่างชัดเจนที่นี่ว่ามีข้อ จำกัด เกี่ยวกับจำนวนอินเตอร์เฟสอินเตอร์เฟสซึ่งต้องไม่เกิน 65535 ข้อมูลการเริ่มต้นเฉพาะของ proxyclasscache มีดังนี้:
proxyclassCache = new beakcache <> (new KeyFactory (), ใหม่ proxyclassFactory ());
ตรรกะเฉพาะสำหรับการสร้างคลาสพร็อกซีถูกสร้างขึ้นผ่านวิธีการใช้ของ ProxyclassFactory
ตรรกะใน ProxyclassFactory รวมถึงตรรกะการสร้างของชื่อแพ็คเกจการเรียก Proxygenerator GenerateProxyclass เพื่อสร้างคลาสพร็อกซีและโหลด Bytecode คลาสพร็อกซีลงใน JVM
1. ตรรกะการสร้างชื่อแพ็คเกจเริ่มต้นคือ com.sun.proxy หากคลาสพร็อกซีเป็นอินเทอร์เฟซพร็อกซีที่ไม่ใช่สาธารณะจะใช้ชื่อแพ็คเกจเดียวกับอินเตอร์เฟสคลาสพร็อกซี ชื่อคลาสเริ่มต้นคือ $ Proxy Plus ค่าจำนวนเต็มที่เพิ่มขึ้นด้วยตนเอง
2. หลังจากชื่อแพ็คเกจพร้อมใช้งานพร็อกซีไบต์จะถูกสร้างขึ้นตามอินเตอร์เฟสที่เข้ามาเฉพาะผ่าน Proxygenerator GenerateProxyclass พารามิเตอร์ -dsun.misc.proxygenerator.saveGeneratedFiles = True มีบทบาทในวิธีนี้ ถ้าเป็นจริงให้บันทึก bytecode ไปที่ดิสก์ ในคลาสพร็อกซีตรรกะวิธีพร็อกซีทั้งหมดจะเหมือนกันในการเรียกใช้วิธีการของ Invocationhander เราสามารถเห็นผลการสลายตัวของพร็อกซีเฉพาะในภายหลัง
3. โหลดไบต์ลงใน JVM ผ่านตัวโหลดคลาสที่ผ่าน: defeleclass0 (โหลดเดอร์, proxyname, proxyclassFile, 0, proxyclassfile.length);
PROXYCLASSFACTORY คลาสส่วนตัวคงที่ใช้ bifunction <classloader, คลาส <?> [], คลาส <? >> {// คำนำหน้าสำหรับคลาสพร็อกซีทั้งหมดชื่อสตริงสุดท้ายคงที่ // หมายเลขถัดไปที่จะใช้สำหรับการสร้างคลาสพร็อกซีที่ไม่ซ้ำกันชื่อส่วนตัวคงที่อะตอมมิกตง nextuniquenumber = new Atomiclong (); @Override คลาสสาธารณะ <?> ใช้ (classloader loader, คลาส <?> [] อินเทอร์เฟซ) {แผนที่ <คลาส <?>, boolean> interfaceset = new IdentityHashMap <> (interfaces.length); สำหรับ (คลาส <?> intf: อินเตอร์เฟส) { / * * ตรวจสอบว่าตัวโหลดคลาสจะแก้ไขชื่อของอินเตอร์เฟส * นี้ไปยังวัตถุคลาสเดียวกัน */ class <?> interfaceclass = null; ลอง {interfaceclass = class.forName (intf.getName (), false, loader); } catch (classnotFoundException e) {} ถ้า (interfaceclass! = intf) {โยน unleglArgumentException ใหม่ (intf + "ไม่สามารถมองเห็นได้จากคลาสรถโหลด"); } / * * ตรวจสอบว่าวัตถุคลาสจริงแสดงถึง * อินเทอร์เฟซ */ if (! interfaceclass.isinterface ()) {โยน unlegalargumentException ใหม่ (interfaceclass.getName () + "ไม่ใช่อินเตอร์เฟส"); } / * * ตรวจสอบว่าอินเทอร์เฟซนี้ไม่ซ้ำกัน */ if (interfaceset.put (interfaceclass, boolean.true)! = null) {โยน unlegalargumentException ใหม่ ("อินเทอร์เฟซซ้ำ:" + interfaceclass.getName ()); }} สตริง proxypkg = null; // แพ็คเกจเพื่อกำหนดคลาสพร็อกซีใน Int AccessFlags = modifier.public | modifier.final; / * * บันทึกแพ็คเกจของอินเทอร์เฟซพร็อกซีที่ไม่ใช่สาธารณะเพื่อให้คลาสพร็อกซี * จะถูกกำหนดไว้ในแพ็คเกจเดียวกัน ตรวจสอบว่า * อินเทอร์เฟซพร็อกซีที่ไม่ใช่สาธารณะทั้งหมดอยู่ในแพ็คเกจเดียวกัน */// สร้างชื่อแพ็คเกจและลอจิกชื่อคลาสสำหรับ (คลาส <?> intf: อินเตอร์เฟส) {int flags = intf.getModifiers (); if (! modifier.ispublic (แฟล็ก)) {accessFlags = modifier.final; ชื่อสตริง = 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 (); สตริง proxyname = proxypkg + proxyclassNamePrefix + num; / * * สร้างคลาสพร็อกซีที่ระบุ สร้าง bytecode ของคลาสพร็อกซี* -dsun.misc.proxygenerator.savegeneratedfiles = จริงเพื่อทำงานในส่วนนี้*/ byte [] proxyclassfile = proxygenerator.generateproxyclass (proxyname, อินเทอร์เฟซ ลอง {// โหลดลงใน JVM return defineclass0 (loader, proxyname, proxyclassFile, 0, proxyclassfile.length); } catch (classformaterror e) { / * * classformaterror ที่นี่หมายความว่า (ยกเว้นข้อผิดพลาดในรหัสการสร้างคลาสพร็อกซี *) มีบางแง่มุม * ที่ไม่ถูกต้องของอาร์กิวเมนต์ที่จัดทำให้กับการสร้างระดับพร็อกซี * (เช่นข้อ จำกัด ของเครื่องเสมือน * เกิน) */ โยน unlegalargumentException ใหม่ (e.toString ()); -เราสามารถถอดรหัสได้ตามรหัสของคลาสพร็อกซีและเราสามารถรับผลลัพธ์ต่อไปนี้ได้ HelloWorld มีวิธี Sayhello เท่านั้น แต่มีสี่วิธีในคลาสพร็อกซีที่มีสามวิธีในวัตถุ: Equals, ToString และ HashCode
โครงสร้างคร่าวๆของตัวแทนประกอบด้วย 4 ส่วน:
แพ็คเกจ com.sun.proxy; นำเข้า com.yo.helloworld; นำเข้า java.lang.reflect.invocationhandler; นำเข้า java.lang.reflect.method; นำเข้า java.lang.reflect.proxy; วิธี M1; วิธีการคงที่ส่วนตัว M3; วิธีการคงที่ส่วนตัว M2; วิธีการคงที่ส่วนตัว M0; สาธารณะ $ proxy0 (InvocationHandler var1) พ่น {super (var1); } บูลีนสุดท้ายสาธารณะเท่ากับ (Object var1) พ่น {ลอง {return ((บูลีน) super.h.invoke (นี่, m1, วัตถุใหม่ [] {var1})). booleanvalue (); } catch (runtimeException | ข้อผิดพลาด var3) {โยน var3; } catch (throwable var4) {โยน undeclaredthrowableException ใหม่ (var4); }} โมฆะสุดท้ายสาธารณะ sayshello () พ่น {ลอง {super.h.invoke (นี่, m3, (วัตถุ []) null); } catch (runtimeException | ข้อผิดพลาด var2) {โยน var2; } catch (throwable var3) {โยน undeclaredthrowableException ใหม่ (var3); }} Public Final String toString () พ่น {ลอง {return (string) super.h.invoke (this, m2, (วัตถุ []) null); } catch (runtimeException | ข้อผิดพลาด var2) {โยน var2; } catch (throwable var3) {โยน undeclaredthrowableException ใหม่ (var3); }} สาธารณะ int สุดท้าย hashCode () พ่น {ลอง {return ((จำนวนเต็ม) super.h.invoke (this, m0, (วัตถุ []) null)). intvalue (); } catch (runtimeException | ข้อผิดพลาด var2) {โยน var2; } catch (throwable var3) {โยน undeclaredthrowableException ใหม่ (var3); }} คงที่ {ลอง {m1 = class.forName ("java.lang.Object"). getMethod ("เท่ากับ", คลาสใหม่ [] {class.forName ("java.lang.Object")}); m3 = class.forname ("com.yao.helloworld"). getMethod ("sayhello", คลาสใหม่ [0]); m2 = class.forname ("java.lang.Object"). getMethod ("toString", คลาสใหม่ [0]); m0 = class.forname ("java.lang.Object"). getMethod ("hashCode", คลาสใหม่ [0]); } catch (nosuchmethodexception var2) {โยน nosuchmethoderror ใหม่ (var2.getMessage ()); } catch (classnotFoundException var3) {โยน noclassDeffoundError ใหม่ (var3.getMessage ()); -คำถามที่พบบ่อย:
1. ToString () HashCode () Equal () วิธีการเรียกตรรกะการเรียกใช้: หากวิธีการในวัตถุทั้งสามนี้เรียกว่าพวกเขาจะผ่านลอจิกของ InvocationHandler เช่นวิธีการและวิธีการอื่น ๆ พวกเขาสามารถมองเห็นได้อย่างชัดเจนจากผลลัพธ์ bytecode ด้านบน วิธีการอื่น ๆ เกี่ยวกับวัตถุจะไม่เป็นไปตามตรรกะการประมวลผลพร็อกซี แต่จะปฏิบัติตามตรรกะวิธีการโดยตรงบนวัตถุที่สืบทอดโดยพร็อกซี
2. เมื่ออินเทอร์เฟซมีวิธีการเท่ากันและวิธีการ hashcode toString มันจะเป็นไปตามตรรกะของตัวจัดการการเรียกร้องเช่นเดียวกับการจัดการวิธีการอินเตอร์เฟสสามัญและเรียกใช้ตรรกะวิธีการตามตรรกะการเขียนใหม่ของวัตถุเป้าหมาย;
3. อินเตอร์เฟสมีลายเซ็นวิธีการซ้ำกันซึ่งอยู่ภายใต้ลำดับที่อินเตอร์เฟสถูกส่งผ่าน ใครก็ตามที่ใช้วิธีการด้านหน้าของมันจะรักษาวิธีเดียวในคลาสพร็อกซีและจะไม่มีลายเซ็นวิธีที่ซ้ำกัน
ขอบคุณสำหรับการอ่านฉันหวังว่ามันจะช่วยคุณได้ ขอบคุณสำหรับการสนับสนุนเว็บไซต์นี้!