JavaRush /Blog Java /Random-VI /Proxy động trong Java

Proxy động trong Java

Xuất bản trong nhóm
Xin chào! Hôm nay chúng ta sẽ xem xét một chủ đề khá quan trọng và thú vị - tạo các lớp proxy động trong Java. Nó không quá đơn giản, vì vậy chúng ta hãy thử tìm hiểu nó bằng các ví dụ :) Vì vậy, câu hỏi quan trọng nhất: proxy động là gì và chúng dùng để làm gì? Lớp proxy là một loại “cấu trúc thượng tầng” so với lớp ban đầu, cho phép chúng ta thay đổi hành vi của nó nếu cần thiết. Thay đổi hành vi có ý nghĩa gì và nó hoạt động như thế nào? Hãy xem xét một ví dụ đơn giản. Giả sử chúng ta có một giao diện Personvà một lớp đơn giản Mantriển khai giao diện này
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);
   }

   //..геттеры, сеттеры, и т.д.
}
Lớp chúng tôi Mancó 3 phương pháp: giới thiệu bản thân, nói tuổi và nói bạn đến từ đâu. Hãy tưởng tượng rằng chúng ta đã nhận được lớp này như một phần của thư viện JAR được tạo sẵn và không thể đơn giản lấy và viết lại mã của nó. Tuy nhiên, chúng ta cần thay đổi hành vi của anh ấy. Ví dụ: chúng tôi không biết phương thức nào sẽ được gọi trên đối tượng của chúng tôi và do đó chúng tôi muốn người đó trước tiên nói “Xin chào!” khi gọi bất kỳ ai trong số họ. (không ai thích một người bất lịch sự). Proxy động - 1Chúng ta nên làm gì trong tình huống như vậy? Chúng ta sẽ cần một vài thứ:
  1. InvocationHandler

Nó là gì? Nó có thể được dịch theo nghĩa đen là "chặn chặn cuộc gọi". Điều này mô tả mục đích của nó khá chính xác. InvocationHandlerlà một giao diện đặc biệt cho phép chúng ta chặn bất kỳ lệnh gọi phương thức nào đến đối tượng của mình và thêm hành vi bổ sung mà chúng ta cần. Chúng ta cần tạo thiết bị chặn của riêng mình - nghĩa là tạo một lớp và triển khai giao diện này. Nó khá đơn giản:
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;
   }
}
Chúng ta chỉ cần triển khai một phương thức giao diện - invoke(). Trên thực tế, nó thực hiện những gì chúng ta cần - nó chặn tất cả các lệnh gọi phương thức đến đối tượng của chúng ta và thêm hành vi cần thiết (ở đây chúng ta invoke()in “Xin chào!” vào bảng điều khiển bên trong phương thức).
  1. Đối tượng ban đầu và proxy của nó.
Hãy tạo một đối tượng ban đầu Manvà một “cấu trúc thượng tầng” (proxy) cho nó:
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());

   }
}
Nhìn không đơn giản lắm! Tôi đã viết nhận xét cụ thể cho từng dòng mã: chúng ta hãy xem xét kỹ hơn những gì đang xảy ra ở đó.

Trong dòng đầu tiên, chúng ta chỉ cần tạo đối tượng ban đầu để tạo proxy. Hai dòng sau đây có thể khiến bạn nhầm lẫn:
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
Nhưng thực sự không có gì đặc biệt xảy ra ở đây :) Để tạo proxy, chúng ta cần ClassLoader(trình nạp lớp) của đối tượng ban đầu và danh sách tất cả các giao diện mà lớp ban đầu của chúng ta (tức là Man) triển khai. Nếu bạn không biết nó là gì ClassLoader, bạn có thể đọc bài viết này về cách tải các lớp vào JVM hoặc bài viết này trên Habré , nhưng đừng bận tâm quá nhiều đến nó. Chỉ cần nhớ rằng chúng ta đang nhận được thêm một ít thông tin mà sau đó chúng ta sẽ cần để tạo đối tượng proxy. Ở dòng thứ tư, chúng ta sử dụng một lớp đặc biệt Proxyvà phương thức tĩnh của nó newProxyInstance():
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Phương pháp này chỉ tạo đối tượng proxy của chúng tôi. Đối với phương thức, chúng tôi chuyển thông tin về lớp ban đầu mà chúng tôi đã nhận được ở bước trước (nó ClassLoadervà danh sách các giao diện của nó), cũng như đối tượng của bộ chặn mà chúng tôi đã tạo trước đó - InvocationHandler'a. Điều chính là đừng quên chuyển đối tượng ban đầu của chúng ta cho bộ chặn vasia, nếu không nó sẽ không có gì để "chặn" :) Chúng ta đã kết thúc với cái gì? Bây giờ chúng ta có một đối tượng proxy vasiaProxy. Nó có thể gọi bất kỳ phương thức giao diện nàoPerson . Tại sao? Bởi vì chúng tôi đã chuyển cho nó danh sách tất cả các giao diện - tại đây:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Bây giờ anh ấy đã "nhận thức được" tất cả các phương thức giao diện Person. Ngoài ra, chúng tôi đã chuyển proxy của mình một đối tượng PersonInvocationHandlerđược định cấu hình để hoạt động với đối tượng đó vasia:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Bây giờ, nếu chúng ta gọi bất kỳ phương thức giao diện nào trên đối tượng proxy Person, thì bộ chặn của chúng ta sẽ “bắt” lệnh gọi này và thay vào đó thực thi phương thức của chính nó invoke(). Hãy thử chạy phương thức này main()! Đầu ra của bảng điều khiển: Xin chào! Tuyệt vời! Chúng tôi thấy rằng thay vì phương thức thực, phương thức của chúng tôi Person.introduce()được gọi : invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hello!");
   return null;
}
Và bảng điều khiển hiển thị “Xin chào!” Nhưng đây không hẳn là hành vi mà chúng tôi muốn nhận:/ Theo ý tưởng của chúng tôi, trước tiên, “Xin chào!” phải được hiển thị, sau đó chính phương thức mà chúng tôi đang gọi sẽ hoạt động. Nói cách khác, phương thức này gọi:
proxyVasia.introduce(vasia.getName());
sẽ xuất ra bảng điều khiển “Xin chào! Tên tôi là Vasya,” chứ không chỉ “Xin chào!” Làm thế nào chúng ta có thể đạt được điều này? Không có gì phức tạp: bạn chỉ cần mày mò một chút với phần chặn và phương thức của chúng tôi invoke():) Hãy chú ý đến những đối số nào được truyền cho phương thức này:
public Object invoke(Object proxy, Method method, Object[] args)
Thay vào đó , một phương thức invoke()có quyền truy cập vào phương thức mà nó được gọi và tất cả các đối số của nó (Phương thức phương thức, Object[] args). Nói cách khác, nếu chúng ta gọi một phương thức proxyVasia.introduce(vasia.getName())và thay vì một phương thức , introduce()một phương thức được gọi invoke()thì bên trong phương thức đó, chúng ta có quyền truy cập vào cả phương thức ban đầu introduce()và đối số của nó! Kết quả là chúng ta có thể làm một cái gì đó như thế này:
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);
   }
}
Bây giờ chúng ta đã thêm invoke()lệnh gọi phương thức ban đầu vào phương thức này. Nếu bây giờ chúng ta thử chạy mã từ ví dụ trước:
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());
   }
}
sau đó chúng ta sẽ thấy rằng bây giờ mọi thứ đều hoạt động như bình thường :) Đầu ra của bảng điều khiển: Xin chào! Tên tôi là Vasya, bạn có thể cần nó ở đâu? Thực tế là rất nhiều nơi. Mẫu thiết kế “proxy động” được sử dụng tích cực trong các công nghệ phổ biến... và nhân tiện, tôi quên nói với bạn rằng Dynamic Proxyđó là một mẫu ! Xin chúc mừng, bạn đã học được một điều khác! :) Proxy động - 2Vì vậy, nó được sử dụng tích cực trong các công nghệ và framework phổ biến liên quan đến bảo mật. Hãy tưởng tượng rằng bạn có 20 phương thức mà chỉ người dùng đã đăng nhập vào chương trình của bạn mới có thể thực thi. Bằng cách sử dụng các kỹ thuật bạn đã học, bạn có thể dễ dàng thêm vào 20 phương pháp này một tính năng kiểm tra xem người dùng đã nhập thông tin đăng nhập và mật khẩu hay chưa mà không cần sao chép mã xác minh riêng biệt trong từng phương pháp. Hoặc, ví dụ: nếu bạn muốn tạo nhật ký nơi tất cả các hành động của người dùng sẽ được ghi lại, điều này cũng dễ dàng thực hiện bằng cách sử dụng proxy. Bây giờ bạn thậm chí có thể: chỉ cần thêm mã vào ví dụ để tên của phương thức được hiển thị trong bảng điều khiển khi được gọi invoke()và bạn sẽ nhận được một nhật ký đơn giản về chương trình của chúng tôi :) Ở cuối bài giảng, hãy chú ý đến một điều quan trọng hạn chế . Việc tạo một đối tượng proxy xảy ra ở cấp độ giao diện chứ không phải ở cấp độ lớp. Một proxy được tạo cho giao diện. Hãy nhìn vào mã này:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Ở đây chúng tôi tạo một proxy dành riêng cho giao diện Person. Nếu chúng ta cố gắng tạo một proxy cho lớp, nghĩa là chúng ta thay đổi loại liên kết và cố gắng truyền tới lớp đó Man, sẽ không có gì hoạt động.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
Ngoại lệ trong luồng "chính" java.lang.ClassCastException: com.sun.proxy.$Proxy0 không thể truyền tới Man Sự hiện diện của giao diện là yêu cầu bắt buộc. Proxy hoạt động ở cấp độ giao diện. Đó là tất cả cho ngày hôm nay :) Là tài liệu bổ sung về chủ đề proxy, tôi có thể giới thiệu cho bạn một video xuất sắc và cũng là một bài viết hay . Chà, bây giờ thật tốt khi giải quyết được một số vấn đề! :) Thấy bạn!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION