คำนำ
"Dynamic Agent" มีต้นกำเนิดมาจากโหมดพร็อกซีในโหมดการออกแบบและโหมดพร็อกซีใช้วัตถุพร็อกซีเพื่อดำเนินการตามคำขอของผู้ใช้และบล็อกการเข้าถึงวัตถุจริงของผู้ใช้
เพื่อให้ตัวอย่างที่ง่ายที่สุดเราต้องการ "FQ" เพื่อเข้าถึงเว็บไซต์ต่างประเทศเนื่องจากเราไม่มี IPs ต่างประเทศทั้งหมดคุณสามารถส่งคำขอของคุณไปยังโฮสต์ต่างประเทศที่ไม่ได้ถูกบล็อกและจากนั้นคุณจะส่งต่อการร้องขอไปยังปลายทางโดยกำหนดค่าโฮสต์ต่างประเทศและส่งต่อกลับไปยังโฮสต์ในประเทศของเราหลังจากได้รับข้อความตอบกลับ
ในตัวอย่างนี้โฮสต์ต่างประเทศเป็นวัตถุพร็อกซีและโฮสต์ที่ถูกทิ้งโดยผนังเป็นวัตถุจริง เราไม่สามารถเข้าถึงวัตถุจริงได้โดยตรง แต่เราสามารถเข้าถึงได้ทางอ้อมผ่านพร็อกซี
ข้อดีอย่างหนึ่งของโหมดพร็อกซีคือคำขอภายนอกทั้งหมดผ่านวัตถุพร็อกซีและวัตถุพร็อกซีมีสิทธิ์ควบคุมว่าคุณได้รับอนุญาตให้เข้าถึงวัตถุจริงอย่างแท้จริงหรือไม่ หากมีการร้องขอที่ผิดกฎหมายวัตถุพร็อกซีสามารถปฏิเสธคุณได้อย่างสมบูรณ์โดยไม่มีปัญหากับวัตถุจริง
หนึ่งในแอพพลิเคชั่นทั่วไปของโหมดพร็อกซีคือเฟรมเวิร์กสปริง AOP ของฤดูใบไม้ผลิใช้การเขียนโปรแกรมที่มุ่งเน้นด้านเพื่อแยกตรรกะทางธุรกิจจริงออกจากข้อยกเว้นบันทึกที่เกี่ยวข้องและข้อมูลอื่น ๆ ทุกครั้งที่คุณขอตรรกะทางธุรกิจจะสอดคล้องกับวัตถุพร็อกซี นอกเหนือจากการตรวจสอบการอนุญาตที่จำเป็นและการพิมพ์บันทึกวัตถุพร็อกซีนี้เป็นบล็อกการประมวลผลตรรกะทางธุรกิจจริง
พร็อกซีคงที่
มีผู้ดำเนินการหลักสองคนของโมเดลพร็อกซี "พร็อกซีคงที่" และ "พร็อกซีแบบไดนามิก" ความแตกต่างที่สำคัญระหว่างสองสิ่งนี้คือคลาสพร็อกซีในอดีตต้องการการเข้ารหัสด้วยตนเองโดยโปรแกรมเมอร์ในขณะที่คลาสพร็อกซีหลังถูกสร้างขึ้นโดยอัตโนมัติ ดังนั้นนี่คือเหตุผลที่คุณแทบจะไม่เคยได้ยินแนวคิดเรื่อง "พร็อกซีคงที่" แน่นอนว่ามันง่ายกว่าที่จะเข้าใจ "พร็อกซีแบบไดนามิก"
สิ่งหนึ่งที่คุณต้องมีความชัดเจนคือพร็อกซีออบเจ็กต์พร็อกซีวิธีการทั้งหมดของวัตถุจริงนั่นคือวัตถุพร็อกซีจำเป็นต้องให้ชื่อวิธีการเดียวกันอย่างน้อยที่สุดกับวัตถุจริงสำหรับการโทรดังนั้นวัตถุพร็อกซีจำเป็นต้องกำหนดวิธีทั้งหมดที่เป็นเจ้าของโดยวัตถุจริงรวมถึงวิธีการในชั้นเรียนหลัก
มาดูตัวอย่างพร็อกซีแบบคงที่อย่างง่าย:
เพื่อแสดงให้เห็นถึงปัญหาเรากำหนดอินเทอร์เฟซ iservice และปล่อยให้คลาสจริงของเราสืบทอดและใช้อินเทอร์เฟซเพื่อให้มีสองวิธีในคลาสจริงของเรา
ดังนั้นคลาสพร็อกซีควรกำหนดให้เสร็จสมบูรณ์สำหรับพร็อกซีสำหรับวัตถุจริง?
โดยทั่วไปแล้วสาระสำคัญของคลาสพร็อกซีคือการกำหนดวิธีการทั้งหมดในคลาสจริงและเพิ่มการดำเนินการอื่น ๆ ภายในวิธีการและในที่สุดก็เรียกวิธีการของคลาสจริง
คลาสพร็อกซีจำเป็นต้องพร็อกซีวิธีการทั้งหมดในคลาสจริงนั่นคือวิธีการที่เหมือนกับวิธีการเหล่านั้นในชั้นเรียนจริงและวิธีการของคลาสจริงจะถูกเรียกทางอ้อมภายในวิธีการเหล่านี้
โดยทั่วไปแล้วคลาสพร็อกซีจะเลือกที่จะสืบทอดอินเทอร์เฟซทั้งหมดและคลาสแม่ของคลาสจริงโดยตรงเพื่อให้ได้ลายเซ็นของผู้ปกครองทั้งหมดของคลาสจริงนั่นคือเพื่อให้วิธีการหลักทั้งหมดเป็นครั้งแรก
ถัดไปพร็อกซีไม่ใช่วิธีการหลักในชั้นเรียนจริง ในตัวอย่างที่นี่วิธี Doservice เป็นวิธีการของคลาสจริง คลาสพร็อกซีของเรายังต้องกำหนดวิธีการที่มีลายเซ็นวิธีเดียวกันกับพร็อกซี
ด้วยวิธีนี้แม้ว่าคลาสพร็อกซีของเราจะเสร็จสมบูรณ์ แต่วิธีการทั้งหมดในชั้นเรียนจริงสามารถพร็อกซีผ่านคลาสพร็อกซีในอนาคต แบบนี้:
โมฆะคงที่สาธารณะหลัก (String [] args) {RealClass RealClass = new RealClass (); proxyclass proxyclass = new proxyclass (RealClass); Proxyclass.sayhello (); proxyclass.doservice ();}Proxyclass เป็นวัตถุพร็อกซีคลาสสามารถพร็อกซีวิธีการทั้งหมดในคลาสจริงและพิมพ์ข้อมูล "ไม่สำคัญ" บางส่วนก่อนที่จะดำเนินการวิธีการเหล่านี้
นี่คือแนวคิดการใช้งานพื้นฐานของโมเดลพร็อกซี แต่ความแตกต่างระหว่างพร็อกซีแบบไดนามิกและพร็อกซีแบบคงที่ประเภทนี้คือพร็อกซีแบบไดนามิกไม่ต้องการคำจำกัดความวิธีการของเราทีละคนและเครื่องเสมือนจริงจะสร้างวิธีการเหล่านี้โดยอัตโนมัติ
กลไกพร็อกซีไดนามิกของ JDK
สิ่งที่แยกแยะพร็อกซีแบบไดนามิกจากพร็อกซีแบบคงที่คือคลาสพร็อกซีของพร็อกซีแบบไดนามิกถูกสร้างขึ้นแบบไดนามิกโดยเครื่องเสมือนจริงที่รันไทม์และเคลียร์เมื่อถอนการติดตั้งเครื่องเสมือน
เรานำคลาสที่ใช้ในพร็อกซีแบบคงที่ข้างต้นกลับมาใหม่เพื่อดูว่าพร็อกซีแบบไดนามิกของ JDK สามารถใกล้เคียงกับวิธีการทั้งหมดของอินสแตนซ์ของคลาสที่แน่นอนได้อย่างไร
กำหนดคลาสการประมวลผลตัวจัดการ:
พร็อกซี API แบบไดนามิกในฟังก์ชั่นหลักเรียก JDK เพื่อสร้างอินสแตนซ์คลาสพร็อกซี:
ยังมีรหัสที่เกี่ยวข้องมากมายลองวิเคราะห์ทีละบิต ก่อนอื่น RealClass ใช้อินเทอร์เฟซ Iservice เป็นคลาสพร็อกซีของเราและกำหนดวิธีการของตัวเองภายใน
ต่อไปเราจะกำหนดคลาสการประมวลผลที่สืบทอดอินเตอร์เฟส Intripation HANDLER และใช้วิธีการเรียกใช้ที่ไม่ซ้ำกัน นอกจากนี้เราต้องประกาศฟิลด์สมาชิกเพื่อจัดเก็บวัตถุจริงนั่นคือวัตถุพร็อกซีเนื่องจากวิธีการใด ๆ ที่เราพร็อกซีนั้นขึ้นอยู่กับวิธีการที่เกี่ยวข้องของวัตถุจริง
เกี่ยวกับบทบาทของวิธีการเรียกใช้นี้และความสำคัญของพารามิเตอร์ที่เป็นทางการต่างๆเราจะทำการวิเคราะห์โดยละเอียดเมื่อเราสะท้อนซอร์สโค้ดพร็อกซี
ในที่สุดกำหนดคลาสการประมวลผลของเราและดำเนินการพร็อกซีแบบไดนามิกตาม JDK วิธีการหลักคือวิธี newproxyinstance ของคลาสพร็อกซี วิธีนี้มีพารามิเตอร์สามตัว หนึ่งคือตัวโหลดคลาสที่สองคือคอลเลกชันของอินเทอร์เฟซทั้งหมดที่ใช้โดยคลาสพร็อกซีและที่สามคือคลาสโปรเซสเซอร์ที่กำหนดเองของเรา
เครื่องเสมือนใช้ตัวโหลดคลาสที่คุณให้ไว้ที่รันไทม์โหลดคลาสอินเตอร์เฟสที่ระบุทั้งหมดลงในพื้นที่เมธอดจากนั้นสะท้อนและอ่านวิธีการในอินเทอร์เฟซเหล่านี้และรวมคลาสโปรเซสเซอร์เพื่อสร้างประเภทพร็อกซี
ประโยคสุดท้ายอาจเป็นนามธรรมเล็กน้อย จะ "รวมกับคลาสโปรเซสเซอร์เพื่อสร้างพร็อกซี" ได้อย่างไร? ในเรื่องนี้เราระบุพารามิเตอร์การเริ่มต้นของเครื่องเสมือนและปล่อยให้มันบันทึกไฟล์คลาสที่สร้างขึ้นของคลาสพร็อกซี
-dsun.misc.proxygenerator.savegeneratedfiles = true
เราถอดรหัสไฟล์คลาสนี้ผ่านเครื่องมือของบุคคลที่สามและมีเนื้อหามากมาย เราแยกการวิเคราะห์:
ก่อนอื่นชื่อของคลาสพร็อกซีนี้สุ่มมาก หากมีคลาสพร็อกซีหลายคลาสที่จะสร้างในโปรแกรม "$ proxy + number" เป็นชื่อคลาสของพวกเขา
ถัดไปคุณจะสังเกตเห็นว่าคลาสพร็อกซีนี้สืบทอดคลาสพร็อกซีและอินเตอร์เฟส iservice ที่เราระบุ (ก่อนหน้านี้หากมีการระบุหลายอินเทอร์เฟซหลายอินเตอร์เฟสจะสืบทอดที่นี่)
จากนั้นคุณจะพบว่าตัวสร้างนี้ต้องใช้พารามิเตอร์ประเภท InchocationHandler และร่างกายของตัวสร้างคือการส่งอินสแตนซ์ InvocationHandler นี้ไปยังฟิลด์ที่สอดคล้องกันของพร็อกซีคลาสแม่สำหรับการจัดเก็บ นี่เป็นหนึ่งในเหตุผลที่คลาสพร็อกซีทั้งหมดจะต้องใช้พร็อกซีเป็นคลาสหลักซึ่งคือการเผยแพร่ฟิลด์ ravecessionhandler ในคลาสหลัก เราจะรู้ในภายหลังว่าการออกแบบขนาดเล็กนี้จะนำไปสู่ข้อเสียร้ายแรงของพร็อกซีแบบไดนามิกที่ใช้ JDK ซึ่งจะเปิดตัวในภายหลัง
เนื้อหานี้ยังเป็นส่วนสำคัญของคลาสพร็อกซี มันจะถูกดำเนินการเมื่อเครื่องเสมือนคงที่เริ่มต้นคลาสพร็อกซี โค้ดขนาดใหญ่ชิ้นนี้เสร็จสิ้นฟังก์ชั่นของการสะท้อนวิธีการทั้งหมดในอินเทอร์เฟซและวิธีการที่สะท้อนทั้งหมดจะถูกเก็บไว้ซึ่งสอดคล้องกับเขตข้อมูลประเภทวิธี
นอกจากนี้เครื่องเสมือนยังสะท้อนถึงวิธีการทั่วไปสามวิธีในวัตถุนั่นคือคลาสพร็อกซีจะพร็อกซีวัตถุจริงที่สืบทอดมาจากวัตถุ
ในส่วนสุดท้ายสิ่งที่เราเห็นคือเครื่องเสมือนสะท้อนวิธีการทั้งหมดที่จะพร็อกซีขึ้นอยู่กับบล็อกรหัสเริ่มต้นแบบคงที่และสร้างวิธีการพร็อกซีสำหรับพวกเขา
วิธีการเหล่านี้ดูเหมือนโค้ดจำนวนมาก แต่อันที่จริงแล้วพวกเขาเป็นเพียงหนึ่งบรรทัดของรหัสซึ่งนำคลาสโปรเซสเซอร์ที่เก็บไว้ในระหว่างการสร้างอินสแตนซ์จากพร็อกซีคลาสแม่และเรียกวิธีการเรียกใช้
พารามิเตอร์ของวิธีการนั้นเหมือนกัน พารามิเตอร์แรกคืออินสแตนซ์ระดับพร็อกซีปัจจุบัน (พิสูจน์ได้ว่ามันไร้ประโยชน์ที่จะส่งผ่านพารามิเตอร์นี้ในอดีต) พารามิเตอร์ที่สองคืออินสแตนซ์วิธีการและพารามิเตอร์ที่สามคือชุดพารามิเตอร์ที่เป็นทางการของวิธีการ ถ้าไม่เป็นโมฆะ
ทีนี้มาดูคลาสโปรเซสเซอร์ที่กำหนดเอง:
วิธีการคลาสพร็อกซีทั้งหมดจะเรียกวิธีการเรียกใช้ของคลาสโปรเซสเซอร์และส่งผ่านในวิธีการปัจจุบันของคลาสพร็อกซี วิธีการเรียกใช้นี้สามารถเลือกที่จะทำให้วิธีการถูกเรียกตามปกติหรือข้ามการโทรวิธีและยังทำสิ่งพิเศษก่อนและหลังวิธีการที่เรียกจริง
นี่คือแนวคิดหลักของพร็อกซีไดนามิก JDK มาสรุปกระบวนการโทรทั้งหมดสั้น ๆ
ก่อนอื่นคำจำกัดความของคลาสโปรเซสเซอร์เป็นสิ่งจำเป็นและจะต้องเชื่อมโยงกับวัตถุจริงนั่นคืออินสแตนซ์ของคลาสพร็อกซี
ต่อไปเราเรียกวิธีการใด ๆ ของคลาสพร็อกซีจากภายนอกและจากซอร์สโค้ดที่ถอดรหัสเรารู้ว่าวิธีการคลาสพร็อกซีจะเรียกใช้วิธีการเรียกใช้โปรเซสเซอร์และส่งผ่านในเมธอดลายเซ็นและชุดพารามิเตอร์อย่างเป็นทางการ
ในที่สุดไม่ว่าจะเป็นวิธีการที่เรียกว่าตามปกติขึ้นอยู่กับว่าตัวประมวลผลเรียกใช้วิธีการจริงหรือไม่จริงวิธีการวิธีการนั้น
ในความเป็นจริงพร็อกซีแบบไดนามิกที่ดำเนินการตาม JDK นั้นมีข้อบกพร่องและข้อบกพร่องเหล่านี้ไม่ใช่เรื่องง่ายที่จะแก้ไขดังนั้น CGLIB จึงเป็นที่นิยม
ข้อบกพร่องและข้อบกพร่องบางอย่าง
กลไกพร็อกซีเดี่ยว
ฉันไม่รู้ว่าคุณสังเกตเห็นว่าตัวอย่างข้างต้นไม่สามารถใช้ได้หรือไม่ พร็อกซีคลาสที่สร้างขึ้นโดยเครื่องเสมือนได้สืบทอดคลาสพร็อกซีเพื่อจัดเก็บอินสแตนซ์คลาสโปรเซสเซอร์ของตัวเองต่อสาธารณะ นั่นหมายความว่าอย่างไร?
การสืบทอดรูทเดียวของ Java จะบอกคุณว่าคลาสพร็อกซีไม่สามารถสืบทอดคลาสอื่นได้อีกต่อไปดังนั้นวิธีการในคลาสหลักของคลาสพร็อกซีจะไม่สามารถรับได้โดยธรรมชาตินั่นคือคลาสพร็อกซีไม่สามารถพร็อกซีวิธีการใด ๆ ของคลาสแม่ในคลาสจริง
นอกเหนือจากนี้เป็นอีกรายละเอียดเล็ก ๆ น้อย ๆ ฉันสงสัยว่าคุณสังเกตเห็นหรือไม่ ฉันเขียนแบบนี้
วิธี Sayhello ที่นี่คืออินเทอร์เฟซ iservice ที่ใช้งานในขณะที่วิธี Doservice เป็นวิธีที่เป็นของตัวเองของ Realclass แต่เราไม่เห็นวิธีนี้จากคลาสพร็อกซีซึ่งหมายความว่าวิธีนี้ไม่ได้เป็นพร็อกซี
ดังนั้นกลไกพร็อกซีแบบไดนามิกของ JDK จึงเป็นโสดและสามารถใช้วิธีพร็อกซีในการรวบรวมอินเทอร์เฟซของคลาสพร็อกซีเท่านั้น
มูลค่าผลตอบแทนที่ไม่เป็นมิตร
โปรดทราบว่า Newproxyinstance ส่งคืนอินสแตนซ์ของคลาสพร็อกซี "$ proxy0" แต่มันถูกส่งคืนเป็นประเภทวัตถุและคุณไม่สามารถบังคับอินสแตนซ์ของวัตถุไปยังประเภท "$ proxy0"
แม้ว่าเราจะรู้ว่าอินสแตนซ์ของวัตถุนี้จริง ๆ แล้วประเภท "$ proxy0" ประเภท "$ proxy0" ไม่มีอยู่ในช่วงระยะเวลาการรวบรวมและคอมไพเลอร์จะไม่อนุญาตให้คุณบังคับให้เป็นประเภทที่ไม่มีอยู่จริง ดังนั้นโดยทั่วไปแล้วจะบังคับให้เป็นหนึ่งในอินเทอร์เฟซที่ดำเนินการโดยคลาสพร็อกซีนี้
RealClass RC = ใหม่ RealClass (); MyHanlder Hanlder = New MyHanlder (RC); ISERVICE OBJ = (ISERVICE) PROXY.NEWPROXYINSTANCE (rc.getClass (). getClassLoader ()
โปรแกรมรันเอาท์พุท:
พร็อกซีเริ่มต้น ...... สวัสดีโลก ... การสิ้นสุดของพร็อกซี ......
จากนั้นคำถามก็มาอีกครั้ง หากคลาสพร็อกซีของเราใช้หลายอินเทอร์เฟซคุณควรบังคับให้ใช้อินเทอร์เฟซประเภทใด ตอนนี้สมมติว่าคลาสพร็อกซีใช้อินเทอร์เฟซ A และ B ดังนั้นหากอินสแตนซ์สุดท้ายถูกบังคับให้ A คุณไม่สามารถเรียกวิธีการทั้งหมดในอินเตอร์เฟส B ที่ดำเนินการโดยคลาสพร็อกซีและในทางกลับกัน
สิ่งนี้นำไปสู่ผลลัพธ์โดยตรง คุณต้องรู้ว่าวิธีใดในอินเทอร์เฟซ หากคุณบังคับให้เข้ากับอินเทอร์เฟซที่เกี่ยวข้องก่อนที่จะเรียกวิธีการมันค่อนข้างไม่เป็นมิตร
ข้างต้นคือสิ่งที่เราคิดว่าไม่ได้สง่างามในกลไกพร็อกซีแบบไดนามิกตาม JDK แน่นอนว่าข้อดีของมันนั้นยิ่งใหญ่กว่าข้อเสียเหล่านี้อย่างแน่นอน ในบทความถัดไปเราจะแนะนำห้องสมุดพร็อกซีแบบไดนามิกแบบไดนามิกแบบไดนามิกที่ใช้กันอย่างแพร่หลายโดยเฟรมเวิร์กต่างๆ เลเยอร์พื้นฐานของมันขึ้นอยู่กับเฟรมเวิร์กการดำเนินงานของไบต์และไม่ต้องอาศัยการสืบทอดการใช้งานอีกต่อไปการแก้ไขข้อบกพร่องของพร็อกซีเดี่ยวของ JDK อย่างสมบูรณ์แบบ
รหัสรูปภาพและไฟล์ทั้งหมดในบทความจะถูกเก็บไว้ในคลาวด์บน GitHub ของฉัน:
(https://github.com/singleyam/overview_java)
คุณยังสามารถเลือกที่จะดาวน์โหลดในพื้นที่
สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่าเนื้อหาของบทความนี้จะมีค่าอ้างอิงบางอย่างสำหรับการศึกษาหรือที่ทำงานของทุกคน หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร ขอบคุณสำหรับการสนับสนุน Wulin.com