JavaRush /จาวาบล็อก /Random-TH /พร็อกซีแบบไดนามิกใน Java

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

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

   public void introduce(String name);

   public void sayAge(int age);

   public void sayFrom(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("Меня зовут " + this.name);
   }

   @Override
   public void sayAge(int age) {
       System.out.println("Мне " + this.age + " years");
   }

   @Override
   public void sayFrom(String city, String country) {

       System.out.println("Я из города " + this.city + ", " + this.country);
   }

   //..геттеры, сеттеры, и т.д.
}
ชั้นเรียนของเราManมี 3 วิธี ได้แก่ แนะนำตัวเอง บอกอายุ และบอกว่าคุณมาจากไหน ลองจินตนาการว่าเราได้รับคลาสนี้โดยเป็นส่วนหนึ่งของไลบรารี JAR สำเร็จรูป และไม่สามารถรับและเขียนโค้ดของคลาสนี้ใหม่ได้ อย่างไรก็ตามเราจำเป็นต้องเปลี่ยนพฤติกรรมของเขา ตัวอย่างเช่น เราไม่รู้ว่าจะมีการเรียกใช้เมธอดใดบนอ็อบเจ็กต์ของเรา ดังนั้นเราจึงต้องการให้บุคคลนั้นกล่าว “สวัสดี!” ก่อนเมื่อทำการเรียกอ็อบเจ็กต์ใดๆ (ไม่มีใครชอบคนไม่สุภาพ) พร็อกซีแบบไดนามิก - 1เราควรทำอย่างไรในสถานการณ์เช่นนี้? เราจะต้องมีบางสิ่ง:
  1. InvocationHandler

มันคืออะไร? สามารถแปลตรงตัวได้ว่า "ตัวดักฟังการโทร" สิ่งนี้อธิบายวัตถุประสงค์ได้ค่อนข้างแม่นยำ InvocationHandlerเป็นอินเทอร์เฟซพิเศษที่ช่วยให้เราสามารถสกัดกั้นการเรียกเมธอดใดๆ ไปยังอ็อบเจ็กต์ของเรา และเพิ่มพฤติกรรมเพิ่มเติมที่เราต้องการ เราจำเป็นต้องสร้าง interceptor ของเราเอง - นั่นคือสร้างคลาสและใช้งานอินเทอร์เฟซนี้ มันค่อนข้างง่าย:
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("Hello!");
       return null;
   }
}
เราจำเป็นต้องใช้วิธีอินเทอร์เฟซเดียวเท่านั้น - invoke(). ในความเป็นจริงมันทำในสิ่งที่เราต้องการ - มันสกัดกั้นการเรียกเมธอดทั้งหมดไปยังอ็อบเจ็กต์ของเราและเพิ่มพฤติกรรมที่จำเป็น (ในที่นี้เราจะinvoke()พิมพ์ "Hello!" ไปยังคอนโซลภายในเมธอด)
  1. วัตถุต้นฉบับและพร็อกซีของมัน
มาสร้างวัตถุดั้งเดิมManและ "โครงสร้างส่วนบน" (พร็อกซี) ให้กับมัน:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());

   }
}
ดูไม่ง่ายเลย! ฉันเขียนความคิดเห็นสำหรับโค้ดแต่ละบรรทัดโดยเฉพาะ: มาดูกันว่าเกิดอะไรขึ้นที่นั่นกันดีกว่า

ในบรรทัดแรก เราเพียงสร้างวัตถุต้นฉบับที่เราจะสร้างพร็อกซี สองบรรทัดต่อไปนี้อาจทำให้คุณสับสน:
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
แต่จริงๆ แล้วไม่มีอะไรพิเศษเกิดขึ้นที่นี่ :) ในการสร้างพรอกซี เราจำเป็นต้องมีClassLoader(ตัวโหลดคลาส) ของออบเจ็กต์ดั้งเดิมและรายการอินเทอร์เฟซทั้งหมดที่คลาสดั้งเดิมของเรา (เช่นMan) นำไปใช้ หากคุณไม่รู้ว่ามันคืออะไรClassLoaderคุณสามารถอ่านบทความนี้เกี่ยวกับการโหลดคลาสลงใน JVM หรืออันนี้บน Habréได้ แต่อย่าเพิ่งกังวลกับมันมากเกินไป เพียงจำไว้ว่าเราได้รับข้อมูลเพิ่มเติมเล็กน้อยที่เราจะต้องสร้างออบเจ็กต์พร็อกซี ในบรรทัดที่สี่เราใช้คลาสพิเศษProxyและวิธีการคงที่newProxyInstance():
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
วิธีการนี้เพิ่งสร้างวัตถุพร็อกซีของเรา ไปยังวิธีที่เราส่งข้อมูลเกี่ยวกับคลาสดั้งเดิมที่เราได้รับในขั้นตอนก่อนหน้า ( ClassLoaderและรายการอินเทอร์เฟซ) รวมถึงวัตถุของ interceptor ที่เราสร้างไว้ก่อนหน้านี้ - InvocationHandler'a สิ่งสำคัญคืออย่าลืมส่งวัตถุดั้งเดิมของเราไปยัง interceptor vasiaมิฉะนั้นมันจะไม่มีอะไรจะ "สกัดกั้น" :) เราได้อะไร? ขณะนี้เรามีวัตถุพรอกvasiaProxyซี มันสามารถเรียกวิธีการอินเทอร์เฟซใดPersonก็ได้ ทำไม เพราะเราส่งรายการอินเทอร์เฟซทั้งหมดไปให้แล้ว - ที่นี่:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
ตอนนี้เขา "ตระหนัก" เกี่ยวกับวิธีการเชื่อมต่อทั้งหมดPersonแล้ว นอกจากนี้เรายังส่งออบเจ็กต์PersonInvocationHandlerที่กำหนดค่าให้ทำงานกับออบเจ็กต์ไปยังพร็อกซี ของเรา vasia:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
ตอนนี้ ถ้าเราเรียกวิธีการอินเทอร์เฟซใดๆ บนวัตถุพร็อกซีPersoninterceptor ของเราจะ "จับ" การโทรนี้และดำเนินการตามวิธีการของตัวเองinvoke()แทน เรามาลองรันเมธอดกันดีกว่าmain()! เอาต์พุตคอนโซล: สวัสดี! ยอดเยี่ยม! เราจะเห็นว่าแทนที่จะเป็นวิธีจริง วิธีการ ของเราPerson.introduce()เรียกว่า: invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hello!");
   return null;
}
และคอนโซลก็แสดงคำว่า “สวัสดี!” แต่นี่ไม่ใช่พฤติกรรมที่เราต้องการได้รับอย่างแน่นอน :/ ตามแนวคิดของเรา ควรแสดง "Hello!" ก่อน จากนั้นวิธีการที่เราเรียกใช้ก็ควรจะได้ผล กล่าวอีกนัยหนึ่ง วิธีการนี้เรียกว่า:
proxyVasia.introduce(vasia.getName());
ควรส่งออกไปยังคอนโซล “Hello! ฉันชื่อวาสยา” และไม่ใช่แค่ “สวัสดี!” เราจะบรรลุเป้าหมายนี้ได้อย่างไร? ไม่มีอะไรซับซ้อน: คุณเพียงแค่ต้องปรับแต่งเล็กน้อยด้วย interceptor และวิธีการของเราinvoke():) ให้ความสนใจกับข้อโต้แย้งที่ส่งผ่านไปยังวิธีนี้:
public Object invoke(Object proxy, Method method, Object[] args)
เมธอดinvoke()สามารถเข้าถึงเมธอดที่ถูกเรียกแทนและอาร์กิวเมนต์ทั้งหมดของมัน (เมธอดเมธอด, Object[] args) กล่าวอีกนัยหนึ่ง ถ้าเราเรียกเมธอดproxyVasia.introduce(vasia.getName())และแทนที่จะเป็นเมธอดintroduce()เมธอดจะถูกเรียกว่าinvoke()ภายในเมธอดนั้น เราสามารถเข้าถึงทั้งเมธอดดั้งเดิมintroduce()และอาร์กิวเมนต์ของมัน! เป็นผลให้เราสามารถทำสิ่งนี้ได้:
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("Hello!");
       return method.invoke(person, args);
   }
}
ตอนนี้เราได้เพิ่มinvoke()การเรียกวิธีการดั้งเดิมให้กับวิธีการแล้ว หากตอนนี้เราพยายามเรียกใช้โค้ดจากตัวอย่างก่อนหน้านี้:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());
   }
}
แล้วเราจะเห็นว่าตอนนี้ทุกอย่างทำงานได้ตามปกติ :) เอาต์พุตคอนโซล: สวัสดี! ฉันชื่อวาสยา คุณต้องการมันที่ไหน? จริงๆแล้วมีหลายสถานที่ รูปแบบการออกแบบ “พร็อกซีแบบไดนามิก” ถูกนำมาใช้อย่างแข็งขันในเทคโนโลยียอดนิยม... และอีกอย่าง ฉันลืมบอกคุณว่าDynamic Proxyมันเป็นรูปแบบ ! ยินดีด้วย คุณได้เรียนรู้อีกหนึ่งอย่างแล้ว! :) พร็อกซีแบบไดนามิก - 2ดังนั้นจึงมีการใช้อย่างแข็งขันในเทคโนโลยีและกรอบการทำงานยอดนิยมที่เกี่ยวข้องกับความปลอดภัย ลองจินตนาการว่าคุณมี 20 วิธีที่สามารถดำเนินการได้โดยผู้ใช้ที่เข้าสู่ระบบของโปรแกรมของคุณเท่านั้น การใช้เทคนิคที่คุณได้เรียนรู้ คุณสามารถเพิ่มการตรวจสอบ 20 วิธีเหล่านี้เพื่อดูว่าผู้ใช้ได้ป้อนข้อมูลเข้าสู่ระบบและรหัสผ่านหรือไม่ โดยไม่ต้องทำซ้ำรหัสยืนยันแยกกันในแต่ละวิธี หรือตัวอย่างเช่น หากคุณต้องการสร้างบันทึกที่จะบันทึกการกระทำของผู้ใช้ทั้งหมด ก็ทำได้ง่ายๆ โดยใช้พรอกซี คุณสามารถทำได้ตอนนี้: เพียงเพิ่มโค้ดลงในตัวอย่างเพื่อให้ชื่อของวิธีการแสดงในคอนโซลเมื่อถูกเรียกinvoke()และคุณจะได้รับบันทึกง่ายๆ ของโปรแกรมของเรา :) ในตอนท้ายของการบรรยายให้ใส่ใจกับสิ่งสำคัญอย่างหนึ่ง ข้อจำกัด การสร้างวัตถุพร็อกซีเกิดขึ้นที่ระดับอินเทอร์เฟซ ไม่ใช่ระดับชั้นเรียน มีการสร้างพร็อกซีสำหรับอินเทอร์เฟซ ดูรหัสนี้:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
ที่นี่เราสร้างพรอกซีสำหรับอินเทอร์เฟซโดยPersonเฉพาะ หากเราพยายามสร้างพร็อกซีสำหรับชั้นเรียน กล่าวคือ เราเปลี่ยนประเภทของลิงก์และพยายามส่งไปที่ชั้นเรียนManจะไม่มีอะไรทำงาน
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
ข้อยกเว้นในเธรด "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 ไม่สามารถส่งไปที่ Man ได้ การมีอินเทอร์เฟซเป็นข้อกำหนดบังคับ พร็อกซีทำงานในระดับอินเทอร์เฟซ นั่นคือทั้งหมดสำหรับวันนี้ :) เพื่อเป็นเนื้อหาเพิ่มเติมในหัวข้อผู้รับมอบฉันทะ ฉันสามารถแนะนำวิดีโอ ที่ยอดเยี่ยม และบทความ ที่ดีให้กับคุณ ได้ ตอนนี้คงจะดีถ้าได้แก้ไขปัญหาบางอย่าง! :) พบกันใหม่!
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION