CodeGym /จาวาบล็อก /สุ่ม /พร็อกซี่แบบไดนามิกใน Java
John Squirrels
ระดับ
San Francisco

พร็อกซี่แบบไดนามิกใน Java

เผยแพร่ในกลุ่ม
สวัสดี! วันนี้เราจะพิจารณาหัวข้อที่ค่อนข้างสำคัญและน่าสนใจ: การสร้างคลาสพร็อกซีไดนามิกใน Java มันไม่ง่ายเลย เราจะพยายามหามันโดยใช้ตัวอย่าง :) ดังนั้นคำถามที่สำคัญที่สุด: พร็อกซีไดนามิกคืออะไรและมีไว้เพื่ออะไร คลาสพร็อกซีเป็น "ส่วนเสริม" ชนิดหนึ่งที่อยู่ด้านบนของคลาสดั้งเดิม ซึ่งช่วยให้เราสามารถเปลี่ยนพฤติกรรมของคลาสดั้งเดิมได้หากจำเป็น “เปลี่ยนพฤติกรรม” หมายความว่าอย่างไรและทำงานอย่างไร? ลองพิจารณาตัวอย่างง่ายๆ สมมติว่าเรามี อินเทอร์เฟซ บุคคลและ คลาส Man แบบธรรมดา ที่ใช้อินเทอร์เฟซนี้

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 ที่มีอยู่ทั่วไป และเราไม่สามารถเขียนโค้ดซ้ำได้ แต่เราต้องเปลี่ยนพฤติกรรมของมันด้วย ตัวอย่างเช่น เราไม่รู้ว่าวัตถุของเราจะเรียกใช้เมธอดใด แต่เราต้องการให้บุคคลนั้นพูดว่า "สวัสดี!" (ไม่มีใครชอบคนไม่สุภาพ) เมื่อเรียกวิธีใดๆ พร็อกซีแบบไดนามิก - 2เราควรทำอย่างไรในสถานการณ์เช่นนี้? เราต้องการบางสิ่ง:
  1. InvocationHandler

นี่คืออะไร? 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()เราส่งออก "สวัสดี!" ไปยังคอนโซล)
  1. วัตถุดั้งเดิมและผู้รับมอบฉันทะ
เราสร้าง วัตถุ Man ดั้งเดิมของเรา และ "ส่วนเสริม" (พร็อกซี) สำหรับมัน:

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
เมื่อใดที่คุณต้องการสิ่งนี้ ที่จริงค่อนข้างบ่อย รูปแบบการออกแบบ "ไดนามิกพร็อกซี" ถูกนำมาใช้อย่างแข็งขันในเทคโนโลยียอดนิยม... อ้อ ฉันลืมบอกไปว่าไดนามิกพร็อกซีเป็นรูปแบบการออกแบบ! ยินดีด้วย คุณได้เรียนรู้อีกหนึ่งอย่างแล้ว! :) พร็อกซี่แบบไดนามิก - 3ตัวอย่างเช่น มีการใช้อย่างแข็งขันในเทคโนโลยียอดนิยมและเฟรมเวิร์กที่เกี่ยวข้องกับความปลอดภัย ลองนึกภาพว่าคุณมี 20 วิธีที่ควรดำเนินการโดยผู้ใช้ที่ลงชื่อเข้าใช้โปรแกรมของคุณเท่านั้น เมื่อใช้เทคนิคที่คุณได้เรียนรู้ คุณสามารถเพิ่มวิธีการตรวจสอบทั้ง 20 วิธีเพื่อดูว่าผู้ใช้ป้อนข้อมูลประจำตัวที่ถูกต้องหรือไม่โดยไม่ต้องใส่รหัสยืนยันซ้ำในแต่ละวิธี หรือสมมติว่าคุณต้องการสร้างบันทึกที่จะบันทึกการกระทำทั้งหมดของผู้ใช้ นอกจากนี้ยังทำได้ง่ายโดยใช้พรอกซี แม้กระทั่งตอนนี้ คุณสามารถเพิ่มโค้ดในตัวอย่างของเราด้านบน เพื่อให้ชื่อเมธอดปรากฏขึ้นเมื่อคุณเรียกinvoke()และนั่นจะสร้างบันทึกที่เรียบง่ายสุด ๆ ของโปรแกรมของเรา :) สรุปแล้ว ให้ใส่ใจกับข้อจำกัดที่สำคัญอย่างหนึ่ง วัตถุพร็อกซีทำงานกับอินเทอร์เฟซ ไม่ใช่คลาส พร็อกซีถูกสร้างขึ้นสำหรับอินเทอร์เฟซ ลองดูที่รหัสนี้:

// 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 ได้ การมีอินเทอร์เฟซเป็นข้อกำหนดที่สมบูรณ์ พร็อกซีทำงานร่วมกับอินเทอร์เฟซ นั่นคือทั้งหมดสำหรับวันนี้ :) ตอนนี้จะเป็นการดีที่จะแก้ปัญหาบางอย่าง! :) จนกว่าจะถึงครั้งต่อไป!
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION