สวัสดี! วันนี้เราจะพิจารณาหัวข้อที่ค่อนข้างสำคัญและน่าสนใจ: การสร้างคลาสพร็อกซีไดนามิกใน Java มันไม่ง่ายเลย เราจะพยายามหามันโดยใช้ตัวอย่าง :) ดังนั้นคำถามที่สำคัญที่สุด: พร็อกซีไดนามิกคืออะไรและมีไว้เพื่ออะไร คลาสพร็อกซีเป็น "ส่วนเสริม" ชนิดหนึ่งที่อยู่ด้านบนของคลาสดั้งเดิม ซึ่งช่วยให้เราสามารถเปลี่ยนพฤติกรรมของคลาสดั้งเดิมได้หากจำเป็น “เปลี่ยนพฤติกรรม” หมายความว่าอย่างไรและทำงานอย่างไร? ลองพิจารณาตัวอย่างง่ายๆ สมมติว่าเรามี อินเทอร์เฟซ บุคคลและ คลาส Man แบบธรรมดา ที่ใช้อินเทอร์เฟซนี้
เราควรทำอย่างไรในสถานการณ์เช่นนี้? เราต้องการบางสิ่ง:
ตัวอย่างเช่น มีการใช้อย่างแข็งขันในเทคโนโลยียอดนิยมและเฟรมเวิร์กที่เกี่ยวข้องกับความปลอดภัย ลองนึกภาพว่าคุณมี 20 วิธีที่ควรดำเนินการโดยผู้ใช้ที่ลงชื่อเข้าใช้โปรแกรมของคุณเท่านั้น เมื่อใช้เทคนิคที่คุณได้เรียนรู้ คุณสามารถเพิ่มวิธีการตรวจสอบทั้ง 20 วิธีเพื่อดูว่าผู้ใช้ป้อนข้อมูลประจำตัวที่ถูกต้องหรือไม่โดยไม่ต้องใส่รหัสยืนยันซ้ำในแต่ละวิธี หรือสมมติว่าคุณต้องการสร้างบันทึกที่จะบันทึกการกระทำทั้งหมดของผู้ใช้ นอกจากนี้ยังทำได้ง่ายโดยใช้พรอกซี แม้กระทั่งตอนนี้ คุณสามารถเพิ่มโค้ดในตัวอย่างของเราด้านบน เพื่อให้ชื่อเมธอดปรากฏขึ้นเมื่อคุณเรียกinvoke()และนั่นจะสร้างบันทึกที่เรียบง่ายสุด ๆ ของโปรแกรมของเรา :) สรุปแล้ว ให้ใส่ใจกับข้อจำกัดที่สำคัญอย่างหนึ่ง วัตถุพร็อกซีทำงานกับอินเทอร์เฟซ ไม่ใช่คลาส พร็อกซีถูกสร้างขึ้นสำหรับอินเทอร์เฟซ ลองดูที่รหัสนี้:
public interface Person {
public void introduce(String name);
public void sayAge(int age);
public void sayWhereFrom(String city, String country);
}
public class Man implements Person {
private String name;
private int age;
private String city;
private String country;
public Man(String name, int age, String city, String country) {
this.name = name;
this.age = age;
this.city = city;
this.country = country;
}
@Override
public void introduce(String name) {
System.out.println("My name is " + this.name);
}
@Override
public void sayAge(int age) {
System.out.println("I am " + this.age + " years old");
}
@Override
public void sayWhereFrom(String city, String country) {
System.out.println("I'm from " + this.city + ", " + this.country);
}
// ...getters, setters, etc.
}
คลาส Man ของเรามี 3 วิธี: แนะนำตัว พูดอายุ และพูดจากที่ไหน ลองนึกภาพว่าเรามีคลาสนี้เป็นส่วนหนึ่งของไลบรารี JAR ที่มีอยู่ทั่วไป และเราไม่สามารถเขียนโค้ดซ้ำได้ แต่เราต้องเปลี่ยนพฤติกรรมของมันด้วย ตัวอย่างเช่น เราไม่รู้ว่าวัตถุของเราจะเรียกใช้เมธอดใด แต่เราต้องการให้บุคคลนั้นพูดว่า "สวัสดี!" (ไม่มีใครชอบคนไม่สุภาพ) เมื่อเรียกวิธีใดๆ 
-
InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PersonInvocationHandler implements InvocationHandler {
private Person person;
public PersonInvocationHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hi!");
return null;
}
}
เราจำเป็นต้องใช้วิธีอินเทอร์เฟซเพียงวิธีเดียว: invoke () และอย่างไรก็ตาม มันทำในสิ่งที่เราต้องการ: มันสกัดกั้นการเรียกเมธอดทั้งหมดไปยังอ็อบเจกต์ของเรา และเพิ่มพฤติกรรมที่จำเป็น (ภายในเมธอดinvoke()เราส่งออก "สวัสดี!" ไปยังคอนโซล)
- วัตถุดั้งเดิมและผู้รับมอบฉันทะ
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// Create the original object
Man arnold = new Man("Arnold", 30, "Thal", "Austria");
// Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
// Call one of our original object's methods on the proxy object
proxyArnold.introduce(arnold.getName());
}
}
มันดูไม่ง่ายเลย! ฉันได้เพิ่มความคิดเห็นสำหรับโค้ดแต่ละบรรทัดโดยเฉพาะ มาดูกันดีกว่าว่าเกิดอะไรขึ้น ในบรรทัดแรก เราเพียงแค่สร้างวัตถุดั้งเดิมที่เราจะสร้างพร็อกซี สองบรรทัดต่อไปนี้อาจทำให้คุณลำบาก:
// Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
อันที่จริง ไม่มีอะไรพิเศษเกิดขึ้นที่นี่ :) ในบรรทัดที่สี่ เราใช้ คลาส Proxy พิเศษ และเมธอดnewProxyInstance()แบบคงที่ของมัน:
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
วิธีนี้เพิ่งสร้างวัตถุพร็อกซีของเรา เราส่งผ่านข้อมูลเกี่ยวกับคลาสดั้งเดิมไปยังเมธอดซึ่งเราได้รับในขั้นตอนสุดท้าย ( ClassLoaderและรายการอินเทอร์เฟซ) รวมถึงวัตถุInvocationHandler ที่สร้างไว้ก่อนหน้านี้ สิ่งสำคัญคืออย่าลืมส่งarnold object ดั้งเดิมของเราไปยังตัวจัดการการร้องขอ มิฉะนั้นจะไม่มีอะไรให้ "จัดการ" :) เราจบลงด้วยอะไร ขณะ นี้เรามีวัตถุพร็อกซี: proxyArnold มันสามารถเรียกใช้วิธีการใด ๆ ของส่วนต่อประสานบุคคล ทำไม เนื่องจากเราได้ให้รายการอินเทอร์เฟซทั้งหมดไว้ที่นี่:
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
ตอนนี้มันรู้เกี่ยวกับวิธีการทั้งหมดของส่วนต่อประสานบุคคล นอกจากนี้ เรายังส่งต่อ วัตถุ PersonInvocationHandlerที่กำหนดค่าให้ทำงานกับ วัตถุ arnold ไปยังพร็อกซีของเรา :
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
ตอนนี้ถ้าเราเรียกใช้เมธอดใดๆ ของ อินเทอร์เฟซ บุคคลบนวัตถุพร็อกซี ตัวจัดการของเราจะสกัดกั้นการโทรและเรียกใช้เมธอดinvoke() ของมันเอง แทน มาลองเรียกใช้ เมธอด main() กัน ! เอาต์พุตคอนโซล:
Hi!
ยอดเยี่ยม! เราเห็นว่าแทนที่จะเป็นเมธอดPerson.introduce() ดั้งเดิม เมธอด invoke()ของPersonInvocationHandler()เรียกว่า:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hi!");
return null;
}
"สวัสดี!" แสดงบนคอนโซล แต่นี่ไม่ใช่ลักษณะการทำงานที่เราต้องการ:/ สิ่งที่เราพยายามทำให้สำเร็จคือการแสดง "สวัสดี!" ก่อน แล้วเรียกเมธอดเดิมนั่นเอง หรืออีกนัยหนึ่ง เมธอดเรียก
proxyArnold.introduce(arnold.getName());
ควรแสดงข้อความว่า "Hi! My name is Arnold" ไม่ใช่แค่ "Hi!" เราจะบรรลุสิ่งนี้ได้อย่างไร? มันไม่ซับซ้อน: เราแค่ต้องใช้เสรีภาพกับตัวจัดการของเราและ เมธอด invoke() :) ให้ความสนใจกับอาร์กิวเมนต์ที่ส่งผ่านไปยังเมธอดนี้:
public Object invoke(Object proxy, Method method, Object[] args)
เมธอดinvoke()สามารถเข้าถึงเมธอดที่เรียกใช้แต่เดิม และอาร์กิวเมนต์ทั้งหมด (เมธอดเมธอด, Object[] args) กล่าวอีกนัยหนึ่ง หากเราเรียกใช้ เมธอด proxyArnold.introduce(arnold.getName())เพื่อให้ เรียกใช้เมธอด invoke()แทนเมธอดIntroduction()ดังนั้นภายในเมธอดนี้เราจะสามารถเข้าถึงเมธอดIntroduction () ดั้งเดิมได้ และข้อโต้แย้งของมัน! เป็นผลให้เราสามารถทำได้:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PersonInvocationHandler implements InvocationHandler {
private Person person;
public PersonInvocationHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hi!");
return method.invoke(person, args);
}
}
ตอนนี้ใน เมธอด invoke()เราได้เพิ่มการเรียกเมธอดดั้งเดิมแล้ว หากตอนนี้เราพยายามรันโค้ดจากตัวอย่างก่อนหน้านี้:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// Create the original object
Man arnold = new Man("Arnold", 30, "Thal", "Austria");
// Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
// Call one of our original object's methods on the proxy object
proxyArnold.introduce(arnold.getName());
}
}
จากนั้นเราจะเห็นว่าตอนนี้ทุกอย่างทำงานได้ตามที่ควร :) เอาต์พุตของคอนโซล:
Hi! My name is Arnold
เมื่อใดที่คุณต้องการสิ่งนี้ ที่จริงค่อนข้างบ่อย รูปแบบการออกแบบ "ไดนามิกพร็อกซี" ถูกนำมาใช้อย่างแข็งขันในเทคโนโลยียอดนิยม... อ้อ ฉันลืมบอกไปว่าไดนามิกพร็อกซีเป็นรูปแบบการออกแบบ! ยินดีด้วย คุณได้เรียนรู้อีกหนึ่งอย่างแล้ว! :) 
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
ที่นี่ เราสร้างพร็อกซีเฉพาะสำหรับอินเทอร์เฟซบุคคล หากเราพยายามสร้างพร็อกซีสำหรับคลาส เช่น เปลี่ยนประเภทการอ้างอิงและพยายามส่งไปยังคลาสManจะไม่ทำงาน
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
proxyArnold.introduce(arnold.getName());
ข้อยกเว้นในเธรด "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 ไม่สามารถส่งไปยัง Man ได้ การมีอินเทอร์เฟซเป็นข้อกำหนดที่สมบูรณ์ พร็อกซีทำงานร่วมกับอินเทอร์เฟซ นั่นคือทั้งหมดสำหรับวันนี้ :) ตอนนี้จะเป็นการดีที่จะแก้ปัญหาบางอย่าง! :) จนกว่าจะถึงครั้งต่อไป!
GO TO FULL VERSION