设计模式 系列 代理

开启 设计模式 探索新篇章

Posted by lichao modified on November 11, 2019

代理模式为其他对象提供一种代理以控制对这个对象的访问。静态代理其实就是设计模式中的经典代理模式。动态代理基于经典代理模式,避免了 Proxy 角色的 class 在系统中冗杂的问题 设计模式

静态代理

设计模式 Subject 定义了 RealSubject 和 Proxy 的公共接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy。

1
2
3
abstract class Subject {
 public abstract void Request();
}

RealSubject 定义 Proxy 所代表的真实实体。

1
2
3
4
5
6
class RealSubject extends Subject {
 @Override
     public void Request() {
  System.out.println("真实的请求");
 }
}

Proxy 保存一个引用使得代理可以访问实体,并提供一个与 Subject 的接口相同的接口,这样代理就可以用来替代实体。

1
2
3
4
5
6
7
8
9
10
class Proxy extends Subject {
 private RealSubject real;
 @Override
     public void Request() {
  if (null == real) {
   real = new RealSubject();
  }
  real.Request();
 }
}

观点

静态代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于 Proxy 和 RealSubject 的功能本质上是相同的,Proxy 只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。

动态代理

在运行状态中,需要代理的地方,根据 Subject 和 RealSubject,动态地创建一个 Proxy,用完之后,就会销毁,这样就可以避免了 Proxy 角色的 class 在系统中冗杂的问题. 设计模式 Java 动态代理基于经典代理模式,引入了一个 InvocationHandler,InvocationHandler 负责统一管理所有的方法调用。JDK 动态代理的实现是基于实现接口的方式,使得 Proxy 和 RealSubject 具有相同的功能。

步骤

  1. 获取 RealSubject 上的所有接口列表;
  2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX;
  3. 根据需要实现的接口信息,在代码中动态创建 该 Proxy 类的字节码;
  4. 将对应的字节码转换为对应的 class 对象
  5. 创建 InvocationHandler 实例 handler,用来处理 Proxy 所有方法调用
  6. Proxy 的 class 对象 以创建的 handler 对象为参数,实例化一个 proxy 对象

在 Java 的动态代理机制中,有两个重要的类(接口),一个是 InvocationHandler 接口、另一个则是 Proxy 类,这一个类和一个接口是实现我们动态代理所必须用到的。InvocationHandler 接口定义:

1
2
3
4
public interface InvocationHandler {
 public Object invoke(Object proxy, Method method, Object[] args)
         throws Throwable;
}

参数说明:

  • proxy - 代理的真实对象。
  • method - 所要调用真实对象的某个方法的 Method 对象
  • args - 所要调用真实对象某个方法时接受的参数

每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个 Handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。

Proxy 类

Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

1
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

这个方法的作用就是得到一个动态的代理对象。参数说明:

  • loader - 一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
  • interfaces - 一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
  • h - 一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上

实例

首先我们定义了一个 Subject 类型的接口,为其声明了两个方法:

1
2
3
4
public interface Subject {
 void hello(String str);
 String bye();
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject 类:

1
2
3
4
5
6
7
8
9
10
11
public class RealSubject implements Subject {
 @Override
     public void hello(String str) {
  System.out.println("Hello  " + str);
 }
 @Override
     public String bye() {
  System.out.println("Goodbye");
  return "Over";
 }
}

下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InvocationHandlerDemo implements InvocationHandler {
 // 这个就是我们要代理的真实对象
 private Object subject;
 // 构造方法,给我们要代理的真实对象赋初值
 public InvocationHandlerDemo(Object subject) {
  this.subject = subject;
 }
 @Override
     public Object invoke(Object object, Method method, Object[] args)
         throws Throwable {
  // 在代理真实对象前我们可以添加一些自己的操作
  System.out.println("Before method");
  System.out.println("Call Method: " + method);
  // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
  Object obj = method.invoke(subject, args);
  // 在代理真实对象后我们也可以添加一些自己的操作
  System.out.println("After method");
  System.out.println();
  return obj;
 }
}

最后,来看看我们的 Client 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Client {
 public static void main(String[] args) {
  // 我们要代理的真实对象
  Subject realSubject = new RealSubject();
  // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
  InvocationHandler handler = new InvocationHandlerDemo(realSubject);
  /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
  Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                  .getClass().getInterfaces(), handler);
  System.out.println(subject.getClass().getName());
  subject.hello("World");
  String result = subject.bye();
  System.out.println("Result is: " + result);
 }
}

我们先来看看控制台的输出:

1
2
3
4
5
6
7
8
9
10
com.sun.proxy.$Proxy0
Before method
Call Method: public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
Hello  World
After method
Before method
Call Method: public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
Goodbye
After method
Result is: Over

在 newProxyInstance 这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是 Subject 类型,所以就可以将其转化为 Subject 类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在 jvm 运行时动态生成的一个对象,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy 为中,最后一个数字表示对象的标号。

1
2
subject.hello("World"); 
String result = subject.bye();

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的 invoke 方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject 类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的 invoke 方法去执行。

我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:

public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String) public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye() 正好就是我们的 Subject 接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的 invoke 方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。