CodeGym /Java 博客 /随机的 /Java 中的动态代理
John Squirrels
第 41 级
San Francisco

Java 中的动态代理

已在 随机的 群组中发布
你好!今天我们将考虑一个相当重要且有趣的话题:Java 中动态代理类的创建。这不是很简单,所以我们将尝试使用示例来弄明白 :) 那么,最重要的问题是:什么是动态代理以及它们的用途是什么?代理类是原始类之上的一种“附加组件”,它允许我们在必要时更改原始类的行为。“改变行为”是什么意思,它是如何运作的?考虑一个简单的例子。假设我们有一个Person接口和一个实现这个接口的 简单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 个方法:introduce、sayAge 和 sayWhereFrom。想象一下,我们把这个类作为现成 JAR 库的一部分,我们不能简单地重写它的代码。但是我们也需要改变它的行为。例如,我们不知道可能会在我们的对象上调用哪个方法,但我们希望我们的人说“嗨!” (没有人喜欢不礼貌的人)调用任何方法时。 动态代理 - 2遇到这种情况怎么办?我们需要一些东西:
  1. 调用处理器

这是什么? 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()方法内,我们向控制台输出“Hi!”)。
  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对象传递给调用处理程序,否则将没有任何东西可以“处理”:) 我们最终得到了什么?我们现在有一个代理对象:proxyArnold。它可以调用Person接口的任何方法。为什么?因为我们在这里给了它所有接口的列表:

// 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));
现在它知道了Person接口 的所有方法。此外,我们向代理传递了一个配置为与arnold对象一起使用的PersonInvocationHandler对象:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
现在,如果我们在代理对象上调用Person接口 的任何方法,我们的处理程序会拦截该调用并改为执行它自己的invoke()方法。让我们尝试运行main()方法!控制台输出:

Hi!
出色的!我们看到调用了PersonInvocationHandler()的invoke()方法,而不是原来的Person.introduce()方法:

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

   System.out.println("Hi!");
   return null;
}
“你好!” 显示在控制台上,但这并不是我们想要的行为:/我们试图实现的是首先显示“Hi!” 然后调用原来的方法本身。换句话说,方法调用

proxyArnold.introduce(arnold.getName());
应该显示“嗨!我叫阿诺德”,而不是简单的“嗨!” 我们怎样才能做到这一点?这并不复杂:我们只需要对我们的处理程序和invoke()方法采取一些自由:) 注意传递给此方法的参数:

public Object invoke(Object proxy, Method method, Object[] args)
invoke ()方法可以访问最初调用的方法及其所有参数(Method 方法、Object[] args)。换句话说,如果我们调用proxyArnold.introduce(arnold.getName())方法以便调用invoke()方法而不是introduce()方法,那么在该方法中我们可以访问原始的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("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));
这里我们专门为Person接口创建了一个代理。如果我们尝试为该类创建一个代理,即更改引用类型并尝试转换为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