CodeGym /Blog Java /Ngẫu nhiên /Proxy động trong Java

Proxy động trong Java

Xuất bản trong nhóm
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 đơn giản lắm, vì vậy chúng tôi sẽ cố gắng tìm ra nó bằng cách sử dụ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 "tiện ích bổ sung" bên trên lớp gốc, cho phép chúng ta thay đổi hành vi của lớp gốc nếu cần. "Thay đổi hành vi" nghĩa là 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ó giao diện Person và lớp Man đơn giản triển khai giao diện này

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.
}
Lớp Man của chúng tôi có 3 phương thức: giới thiệu, sayAge và sayWhereFrom. Hãy tưởng tượng rằng chúng ta có lớp này như một phần của thư viện JAR có sẵn và chúng ta không thể viết lại mã của nó một cách đơn giản. Nhưng chúng ta cũng cần phải thay đổi hành vi của nó. Ví dụ: chúng tôi không biết phương thức nào có thể được gọi trên đối tượng của chúng tôi, nhưng chúng tôi muốn người của mình nói "Xin chào!" (không ai thích ai đó bất lịch sự) khi bất kỳ phương thức nào được gọi. Proxy động - 2Chúng ta nên làm gì trong tình huống này? Chúng ta sẽ cần một số thứ:
  1. Trình xử lý lệnh gọi

Cái này là cái gì? InvocationHandler là 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 một thiết bị chặn của riêng mình, tức là tạo một lớp thực hiện giao diện này. Điều này 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("Hi!");
       return null;
   }
}
Chúng ta chỉ cần triển khai một phương thức giao diện: invoke() . Và, nhân tiện, 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 (bên trong phương thức Invoke() , chúng ta xuất "Xin chào!" ra bàn điều khiển).
  1. Đối tượng ban đầu và các proxy của nó.
Chúng tôi tạo đối tượng Man ban đầu của mình và một "tiện ích bổ sung" (proxy) cho nó:

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());

   }
}
Điều này trông không đơn giản lắm! Tôi đã thêm 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 tôi chỉ cần tạo đối tượng ban đầu mà chúng tôi sẽ tạo proxy. Hai dòng sau đây có thể gây khó khăn cho bạn:

 // 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();
Trên thực tế, không có gì thực sự đặc biệt xảy ra ở đây :) Trong dòng thứ tư, chúng tôi sử dụng lớp Proxy đặc biệt và phương thức newProxyInstance() tĩnh của nó :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Phương pháp này chỉ tạo đối tượng proxy của chúng tôi. Chúng ta chuyển đến phương thức thông tin về lớp ban đầu mà chúng ta đã nhận được ở bước cuối cùng ( ClassLoader của nó và danh sách các giao diện của nó), cũng như đối tượng InvocationHandler đã tạo trước đó . Điều chính là đừng quên chuyển đối tượng arnold ban đầu của chúng ta cho trình xử lý lệnh gọi, nếu không sẽ không có gì để "xử lý" :) Cuối cùng thì chúng ta đã làm được gì? Bây giờ chúng ta có một đối tượng proxy: proxyArnold . Nó có thể gọi bất kỳ phương thức nào của giao diện Person . Tại sao? Bởi vì chúng tôi đã cung cấp cho nó một danh sách tất cả các giao diện ở đây:

// 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));
Bây giờ nó đã biết về tất cả các phương thức của giao diện Person . Ngoài ra, chúng tôi đã chuyển cho 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 arnold :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Bây giờ nếu chúng ta gọi bất kỳ phương thức nào của giao diện Person trên đối tượng proxy, thì trình xử lý của chúng ta sẽ chặn cuộc gọi và thay vào đó thực thi phương thức invoke() của chính nó . Hãy thử chạy phương thức main() ! Đầu ra bảng điều khiển:

Hi!
Xuất sắc! Chúng ta thấy rằng thay vì phương thức Person.introduce() ban đầu , phương thức invoke() của PersonInvocationHandler() của chúng ta được gọi là:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hi!");
   return null;
}
"CHÀO!" được hiển thị trên bảng điều khiển, nhưng đây không chính xác là hành vi mà chúng tôi muốn :/ Những gì chúng tôi đang cố gắng đạt được là hiển thị "Xin chào!" và sau đó gọi chính phương thức ban đầu. Nói cách khác, phương thức gọi

proxyArnold.introduce(arnold.getName());
nên hiển thị "Xin chào! Tên tôi là Arnold", không chỉ đơn giản là "Xin chào!" Làm thế nào chúng ta có thể đạt được điều này? Nó không phức tạp: chúng ta chỉ cần thực hiện một số quyền tự do với trình xử lý của mình và phương thức 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)
Phương thức gọi() có quyền truy cập vào phương thức được gọi ban đầu và tất cả các đối số của nó (Phương thức phương thức, Đối tượng [] args). Nói cách khác, nếu chúng ta gọi phương thức proxyArnold.introduce(arnold.getName()) để phương thức gọi() được gọi thay vì phương thức giới thiệu() , thì bên trong phương thức này, chúng ta có quyền truy cập vào phương thức giới thiệu() ban đầu và lập luận của nó! Kết quả là, chúng ta có thể làm điều 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("Hi!");
       return method.invoke(person, args);
   }
}
Bây giờ trong phương thức Invoke() chúng ta đã thêm một lệnh gọi vào phương thức ban đầu. 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) {

       // 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());
   }
}
sau đó chúng ta sẽ thấy rằng bây giờ mọi thứ hoạt động như bình thường :) Đầu ra bảng điều khiển:

Hi! My name is Arnold
Khi nào bạn có thể cần điều này? Trên thực tế, khá thường xuyên. Mẫu thiết kế "proxy động" được sử dụng tích cực trong các công nghệ phổ biến... Ồ, nhân tiện, tôi quên đề cập rằng Dynamic Proxy là một mẫu thiết kế! Xin chúc mừng, bạn đã học được thêm một! :) Proxy động - 3Ví dụ: nó được sử dụng tích cực trong các công nghệ và khuôn khổ 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 chỉ nên được thực thi bởi những người dùng đã đăng nhập vào chương trình của bạn. 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 kiểm tra để xem liệu người dùng đã nhập thông tin đăng nhập hợp lệ mà không cần sao chép mã xác minh trong mỗi phương pháp. Hoặc giả sử bạn muốn tạo nhật ký để ghi lại tất cả các thao tác của người dùng. Điều này cũng dễ dàng thực hiện bằng cách sử dụng proxy. Ngay cả bây giờ, bạn có thể chỉ cần thêm mã vào ví dụ trên để tên phương thức được hiển thị khi bạn gọi invoke() và điều đó sẽ tạo ra nhật ký siêu đơn giản về chương trình của chúng ta :) Tóm lại, hãy chú ý đến một giới hạn quan trọng. Một đối tượng proxy hoạt động với các giao diện, không phải các lớp. Một proxy được tạo cho một giao diện. Hãy xem mã này:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Ở đây chúng tôi tạo một proxy dành riêng cho giao diện Người . Nếu chúng ta cố gắng tạo proxy cho lớp, tức là thay đổi loại tham chiếu và cố gắng chuyển sang lớp Man , nó sẽ không hoạt động.

Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

proxyArnold.introduce(arnold.getName());
Ngoại lệ trong luồng "chính" java.lang.ClassCastException: com.sun.proxy.$Proxy0 không thể truyền tới Người đàn ông Có một giao diện là một yêu cầu tuyệt đối. Proxy hoạt động với giao diện. Đó là tất cả cho ngày hôm nay :) Chà, bây giờ sẽ tốt hơn nếu bạn giải quyết một số nhiệm vụ! :) Cho đến lần sau!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION