โดยบังเอิญวันนี้ จู่ๆ ฉันก็อยากจะดูไดนามิกพร็อกซีของ JDK เพราะฉันรู้มาบ้างแล้ว และฉันแค่ต้องการทดสอบการใช้งาน ฉันจึงเขียนอินเทอร์เฟซและคลาสเหล่านี้อย่างรวดเร็ว:
คลาสอินเทอร์เฟซ: UserService.java
คัดลอกรหัสรหัส ดังต่อไปนี้:
แพ็คเกจ com.yixi.proxy;
UserService ส่วนต่อประสานสาธารณะ {
สาธารณะ int บันทึก ();
การอัปเดตโมฆะสาธารณะ (int id);
-
คลาสการใช้งาน: UserServiceImpl.java
คัดลอกรหัสรหัส ดังต่อไปนี้:
แพ็คเกจ com.yixi.proxy;
UserServiceImpl ระดับสาธารณะใช้ UserService {
@แทนที่
บันทึก int สาธารณะ () {
System.out.println("บันทึกผู้ใช้...");
กลับ 1;
-
@แทนที่
การอัปเดตโมฆะสาธารณะ (int id) {
System.out.println("อัปเดตผู้ใช้" + id);
-
-
จากนั้นฉันก็เขียน InvocationHandler ที่ฉันต้องการอย่างใจจดใจจ่อ ฟังก์ชั่นนี้ง่ายมาก คือการบันทึกเวลาเริ่มต้นและเวลาสิ้นสุดของการดำเนินการตามเมธอด
TimeInvocationHandler.java
คัดลอกรหัสรหัส ดังต่อไปนี้:
แพ็คเกจ com.yixi.proxy;
นำเข้า java.lang.reflect.InvocationHandler;
นำเข้า java.lang.reflect.Method;
TimeInvocationHandler คลาสสาธารณะใช้ InvocationHandler {
@แทนที่
การเรียกใช้วัตถุสาธารณะ (พร็อกซีวัตถุ, วิธีการวิธีการ, วัตถุ [] args)
ขว้างได้ ขว้างได้ {
System.out.println("startTime : " +System.currentTimeMillis());
วัตถุ obj = method.inurge (พร็อกซี, args);
System.out.println("endTime : " +System.currentTimeMillis());
กลับ obj;
-
-
หลังจากเตรียมการทั้งหมดเสร็จแล้ว ก็ถึงเวลาเริ่มเขียนแบบทดสอบ!
ทดสอบ.java
คัดลอกรหัสรหัส ดังต่อไปนี้:
แพ็คเกจ com.yixi.proxy;
นำเข้า java.lang.reflect.Proxy;
การทดสอบชั้นเรียนสาธารณะ {
โมฆะคงที่สาธารณะ main (String [] args) { 9 TimeInvocationHandler timeHandler = ใหม่ TimeInvocationHandler ();
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
คุณอัปเดต (2);
คุณบันทึก();
-
-
มันวิ่งไปอย่างมีความสุข แต่กลับไม่ปรากฏหน้าคุณเลย ผลลัพธ์ก็คือข้อยกเว้นที่เต็มหน้าจอ:
คัดลอกรหัสรหัส ดังต่อไปนี้:
เวลาเริ่มต้น: 1352877835040
เวลาเริ่มต้น: 1352877835040
เวลาเริ่มต้น: 1352877835040
ข้อยกเว้นในเธรด "main" java.lang.reflect.UndeclaredThrowableException
ที่ $Proxy0.update (ไม่ทราบแหล่งที่มา)
ที่ com.yixi.proxy.Test.main (Test.java:11)
เกิดจาก: java.lang.reflect.InvocationTargetException
ที่ sun.reflect.NativeMethodAccessorImpl.inurge0 (วิธีดั้งเดิม)
ที่ sun.reflect.NativeMethodAccessorImpl.inurge (NativeMethodAccessorImpl.java:39)
ที่ sun.reflect.DelegatingMethodAccessorImpl.inurge (DelegatingMethodAccessorImpl.java:25)
ที่ java.lang.reflect.Method.inrigg (Method.java:597)
ที่ com.yixi.proxy.TimeInvocationHandler.inrigg (TimeInvocationHandler.java:12)
... อีก 2 อัน
com.yixi.proxy.TimeInvocationHandler.invoid(TimeInvocationHandler.java:12) ข้อยกเว้นบอกปัญหาอย่างชัดเจนในบรรทัดที่ 12 ของ TimeInvocationHandle: นั่นคือ
คัดลอกรหัสรหัส ดังต่อไปนี้:
การเรียกใช้วัตถุสาธารณะ (พร็อกซีวัตถุ, วิธีการวิธีการ, วัตถุ [] args)
ขว้างได้ ขว้างได้ {
System.out.println("startTime : " +System.currentTimeMillis());
วัตถุ obj = method.inurge (พร็อกซี, args);
System.out.println("endTime : " +System.currentTimeMillis());
กลับ obj;
-
วิธีนี้ไม่มีอะไรผิดปกติ! เนื่องจากเมธอด invivo() ดูเหมือนว่าจะให้พารามิเตอร์ทั้งหมดที่จำเป็นสำหรับ method.inurge(Object, Object[]) เราจะใช้มันเป็นเรื่องของหลักสูตร หากคุณคิดอย่างนั้นจริงๆ แสดงว่าคุณหลอก JDK ได้ กับดัก มาดูวิธีเขียนที่ถูกต้องกันก่อน เผื่อว่านักเรียนบางคนไม่มีอารมณ์จะอ่านข้อความต่อไปนี้ อย่างน้อยก็ให้คำตอบที่ถูกต้องแก่ฉันด้วย
แก้ไข TimeInvocationHandler.java
คัดลอกรหัสรหัส ดังต่อไปนี้:
แพ็คเกจ com.yixi.proxy;
นำเข้า java.lang.reflect.InvocationHandler;
นำเข้า java.lang.reflect.Method;
TimeInvocationHandler คลาสสาธารณะใช้ InvocationHandler {
วัตถุส่วนตัว o;
TimeInvocationHandler สาธารณะ (วัตถุ o) {
นี่.o = o;
-
@แทนที่
การเรียกใช้วัตถุสาธารณะ (พร็อกซีวัตถุ, วิธีการวิธีการ, วัตถุ [] args)
ขว้างได้ ขว้างได้ {
System.out.println("startTime : " +System.currentTimeMillis());
วัตถุ obj = method.inurge (o, args);
System.out.println("endTime : " +System.currentTimeMillis());
กลับ obj;
-
-
แก้ไข Test.java
คัดลอกรหัสรหัส ดังต่อไปนี้:
แพ็คเกจ com.yixi.proxy;
นำเข้า java.lang.reflect.Proxy;
การทดสอบชั้นเรียนสาธารณะ {
โมฆะคงที่สาธารณะ main (String [] args) {
TimeInvocationHandler timeHandler = TimeInvocationHandler ใหม่ (UserServiceImpl ใหม่ ());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
คุณอัปเดต (2);
คุณบันทึก();
-
-
นี่คือผลลัพธ์ที่ถูกต้อง:
คัดลอกรหัสรหัส ดังต่อไปนี้:
เวลาเริ่มต้น: 1352879531334
อัปเดตผู้ใช้ 2
เวลาสิ้นสุด: 1352879531334
เวลาเริ่มต้น: 1352879531334
ผู้ใช้บันทึก....
เวลาสิ้นสุด: 1352879531335
หากคุณต้องการโค้ดน้อยลง คุณสามารถเขียนคลาสที่ไม่ระบุชื่อได้โดยตรง:
แพ็คเกจ com.yixi.proxy;
นำเข้า java.lang.reflect.InvocationHandler;
นำเข้า java.lang.reflect.Method;
นำเข้า java.lang.reflect.Proxy;
การทดสอบชั้นเรียนสาธารณะ {
โมฆะคงที่สาธารณะ main (String [] args) {
UserServiceImpl สุดท้าย usi = UserServiceImpl ใหม่ ();
UserService u = (บริการผู้ใช้) Proxy.newProxyInstance(
usi.getClass().getClassLoader()
usi.getClass().getInterfaces()
InvocationHandler ใหม่ () {
@แทนที่
การเรียกใช้วัตถุสาธารณะ (พร็อกซีวัตถุ, วิธีการวิธีการ, วัตถุ [] args)
ขว้างได้ ขว้างได้ {
System.out.println("startTime : " +System.currentTimeMillis());
วัตถุ obj = method.inurge (usi, args);
System.out.println("endTime : " +System.currentTimeMillis());
กลับ obj;
-
-
คุณอัปเดต (2);
คุณบันทึก();
-
-
เนื่องจากพารามิเตอร์ตัวแรกใน method.invoid(target,args); เป็นอ็อบเจ็กต์เป้าหมาย เหตุใดเมธอด InvocationHandler จึงจำเป็นต้องมีพารามิเตอร์ Object proxy มาดูข้างล่างกันดีกว่า!
สำหรับวิธีการเรียกใช้ที่สำคัญที่สุด (ในความเห็นส่วนตัวของฉัน) เรามาดูสิ่งที่ JDK กล่าวไว้:
คัดลอกรหัสรหัส ดังต่อไปนี้:
วิงวอน
การเรียกใช้วัตถุ (พร็อกซีวัตถุ
วิธีการวิธีการ
วัตถุ [] args)
พ่นการเรียกเมธอด Throwable handles บนอินสแตนซ์พร็อกซีและส่งกลับผลลัพธ์ เมธอดนี้ถูกเรียกในตัวจัดการการร้องขอเมื่อมีการเรียกเมธอดนี้บนอินสแตนซ์พร็อกซีที่เชื่อมโยงด้วย
พารามิเตอร์:
พร็อกซี - อินสแตนซ์พร็อกซีที่เรียกใช้เมธอด
วิธีการ - อินสแตนซ์ของวิธีการที่สอดคล้องกับวิธีการของอินเทอร์เฟซที่เรียกบนอินสแตนซ์ของพร็อกซี คลาสการประกาศของวัตถุวิธีการจะเป็นอินเทอร์เฟซที่มีการประกาศวิธีการ ซึ่งอาจเป็นอินเทอร์เฟซระดับสูงของอินเทอร์เฟซของพร็อกซีซึ่งคลาสพร็อกซีสืบทอดวิธีการนั้น
args - อาร์เรย์ของอ็อบเจ็กต์ที่มีค่าอาร์กิวเมนต์ที่ส่งผ่านไปยังการเรียกเมธอดบนอินสแตนซ์พร็อกซีหรือเป็นโมฆะหากวิธีอินเทอร์เฟซไม่รับอาร์กิวเมนต์ พารามิเตอร์ของประเภทพื้นฐานจะถูกรวมไว้ในอินสแตนซ์ของคลาส wrapper พื้นฐานที่เหมาะสม (เช่น java.lang.Integer หรือ java.lang.Boolean)
พร็อกซี - อินสแตนซ์พร็อกซีที่เรียกใช้เมธอดนี้ ประโยคนี้หมายถึงอะไร? การแสดงเหรอ? วิธีการเป็นวิธีพร็อกซี? ถ้าอย่างนั้นวิธีการของฉันในการรันพร็อกซีไม่ควรเป็น Object obj = method.invoid(proxy, args);? ตอนนั้นฉันไม่ได้หันกลับมา ฉันไปที่กลุ่มสนทนาและไปที่ Google แต่ไม่พบแรงบันดาลใจใดๆ ฉันคิดว่าฉันควรดูซอร์สโค้ดดีกว่าและบางทีฉันอาจจะเห็นอะไรบางอย่าง!
เปิดซอร์สโค้ดของคลาส Proxy และค้นหาว่า Constructor คืออะไร:
คัดลอกรหัสรหัส ดังต่อไปนี้:
protectedInvocationHandler ชั่วโมง;
พร็อกซีที่ได้รับการป้องกัน (InvocationHandler h) {
นี่.h = h;
-
ใช้ InvocationHandler เป็นพารามิเตอร์ของ Constructor ของ Proxy.... แล้ว InvocationHandler ใช้ทำอะไร? มีการเชื่อมต่อกับเมธอด invocation() ใน InvocationHandler หรือไม่
ความคิดแรกของฉันคือ Proxy จะเรียกคำสั่งต่อไปนี้เป็นการภายใน:
คัดลอกรหัสรหัส ดังต่อไปนี้:
h. วิงวอน (นี่, ชื่อวิธีการ, args);
เนื่องจากคุณต้องเรียกใช้เมธอดการเรียกใช้เพื่อดำเนินการเมธอดที่เกี่ยวข้อง
เรามาดูเรื่องนี้กันก่อน
ที่นี่คุณจะพบกับบางสิ่งที่ดูสมเหตุสมผล: เมื่อ u.update(2), àProxy จะเรียก handler.invoid(proxyClass,update,2) à ซึ่งหมายถึง proxyClass.update(2);
เมื่อ u.save(); àProxy จะเรียก handler.invoid(proxyClass,save,null) àนั่นคือ proxyClass.save();
เมื่อ Test.java เปลี่ยนเป็น:
คัดลอกรหัสรหัส ดังต่อไปนี้:
การทดสอบชั้นเรียนสาธารณะ {
โมฆะคงที่สาธารณะ main (String [] args) {
UserServiceImpl สุดท้าย usi = UserServiceImpl ใหม่ ();
UserService u = (บริการผู้ใช้) Proxy.newProxyInstance(
usi.getClass().getClassLoader()
usi.getClass().getInterfaces()
InvocationHandler ใหม่ () {
@แทนที่
การเรียกใช้วัตถุสาธารณะ (พร็อกซีวัตถุ, วิธีการวิธีการ, วัตถุ [] args)
ขว้างได้ ขว้างได้ {
กลับเป็นโมฆะ;
-
-
คุณอัปเดต (2);
คุณบันทึก();
-
-
โปรดทราบว่าวิธีการของคลาสที่ไม่ระบุชื่อในขณะนี้คืนค่าเป็นโมฆะ หากคุณรันคุณจะพบ:
คัดลอกรหัสรหัส ดังต่อไปนี้:
ข้อยกเว้นในเธรด "main" java.lang.NullPointerException
ที่ $Proxy0.save(ไม่ทราบที่มา)
ที่ com.yixi.proxy.Test.main (Test.java:17)
มีตัวชี้เป็นโมฆะในบรรทัดที่ 17 นั่นคือ u.save() วิธีการที่นี่มีองค์ประกอบที่เป็นโมฆะ เป็นไปได้ไหมว่าคุณว่างเปล่า มันไม่ควรเป็นเช่นนั้น หากคุณเป็นโมฆะ u.update(2) จะรายงานข้อยกเว้นของตัวชี้ null ที่นั่น เมื่อฉันใส่ความคิดเห็นในบรรทัดที่ 17 ข้อยกเว้นจะหายไป ระบุว่า u.update() สามารถดำเนินการได้ตามปกติ แล้วทำไมถึงเป็นเช่นนี้?
นี่คือเหตุผลว่าทำไมวิธีการเรียกใช้จึงคืนค่า null:
ให้ความสนใจกับสองวิธีในคลาส UserService:
คัดลอกรหัสรหัส ดังต่อไปนี้:
UserService ส่วนต่อประสานสาธารณะ {
สาธารณะ int บันทึก ();
การอัปเดตโมฆะสาธารณะ (int id);
-
เมธอด Save() ส่งคืนประเภท int และวิธีการ update ส่งคืนประเภทโมฆะ ตามการเดาข้างต้น handler.invive() ใช้ proxyClass.update(2) และวิธีการ return ในวิธีการเรียกใช้นั้นสอดคล้องกัน ค่าของวิธีพร็อกซี
ดังนั้นเมื่อวิธีการเรียกใช้คืนค่าเป็นโมฆะ วิธีการอัปเดตของตัวแทนจะได้รับค่าส่งคืนเป็นโมฆะ ดังนั้นจึงไม่มีการรายงานข้อยกเว้น และการบันทึกของตัวแทนจะต้องส่งคืนค่าประเภท int สิ่งที่เราส่งคืนที่นี่ยังคงเป็นโมฆะ และ JVM ไม่สามารถแปลงค่าว่างได้ มันถูกแปลงเป็นประเภท int ดังนั้นจึงมีการรายงานข้อยกเว้นอย่างชัดเจน และยังสามารถพิสูจน์การเดาก่อนหน้านี้ได้อีกด้วย
พร็อกซีพารามิเตอร์แรกในวิธีการเรียกใช้ของ InvocationHandler ดูเหมือนว่าจะเป็นเพียงการอนุญาตให้คลาส Proxy ส่งผ่านการอ้างอิงของวัตถุพร็อกซี proxyClass เมื่อเรียกใช้วิธีการด้วยการอ้างอิงของวัตถุ InvocationHandler ของตัวเองเพื่อดำเนินธุรกิจให้เสร็จสมบูรณ์ที่ proxyClass จำเป็นต้องดำเนินการให้เสร็จสิ้น .
ความสามารถทางวรรณกรรมไม่ดี! ความสามารถมีจำกัด! ฉันหวังว่าพวกคุณจะแก้ไขฉันได้ ...