设计模式 代理模式

本文将介绍设计模式中的代理模式。

一、什么是代理模式?

在某些情况下,不希望或无法直接访问对象,此时便可以使用代理模式,增加一个第三方代理对象,通过它作为中介代为调用。

常见的代理形式有:远程代理、保护代理、虚拟代理、缓冲代理、智能引用代理等。

二、实现

1. 结构

2. 角色

  • Subject 抽象主题角色:
    • 声明了 Proxy 和 RealSubject 的共同接口
    • 客户端针对 Subject 进行编程,从而可以一致地对待 Proxy 和 RealSubject
  • Proxy 代理主题角色:
    • 包含了对 RealSubject 的引用,因此可以进行 “代理调用”
    • 还可以负责对 RealSubject 的创建和删除
  • RealSubject 真实主题角色:
    • 实现真正的业务操作

3. 简单示例

Subject:

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

Proxy:

1
2
3
4
5
6
7
8
9
10
public class Proxy extends Subject {

private RealSubject realSubject = new RealSubject();

public void request() {
// ···
realSubject.request();
// ···
}
}

RealSubject:

1
2
3
4
5
public class RealSubject extends Subject {
public void request() {
// ···
}
}

三、不同代理类型

1. 远程代理

为一个处于不同地址空间的对象提供本地的代理服务。

远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户端不用关心被代理的远程对象是否在本地,由远程代理对象承担网络通信工作,负责对远程对象的调用。

2. 虚拟代理

如果需要创建一个资源耗费较大的对象,先窜关键一个消耗相对较小的代理对象来表示,待使用时再创建真实对象。

3. 保护代理

控制对一个对象的访问,可以给不同的用户提供不同的使用权限。

4. 缓冲代理

缓存操作的结果以便复用,避免重复操作。

5. 智能引用代理

提供一些额外操作,例如将对象被调用的次数记录下来。

四、动态代理

1. 静态代理和动态代理

传统的代理模式又称静态代理。在静态代理下,代理类需要事先编写,代理类所能代理的真实主题类是固定的,代理类的增加会导致代码中类的增加。

动态代理可以在运行时根据实际需要动态创建代理类,让同一个代理类能够代理多个不同的真实主题类。

动态代理类是一种较为高级的代理模式,它在事务管理、AOP 等领域都发挥了重要的作用。

2. JDK 对动态代理的支持

(1) 获取代理对象

通过 Proxy 获取代理对象,获取方法有以下两种:

需要注意的是:

  • loader:即类加载器,可以通过 任意对象.getClass().getClassLoader() 获取

    在没有自定义类加载器的情况下,除了 Java 核心类和 Java 扩展类之外,其它类都由系统类加载器进行加载。因此,使用任意类获取类加载器即可。

    具体请看:

    Java 类加载与反射 - 类加载器

  • handler:调用处理者

    InvocationHandler 接口是对调用处理者的抽象,声明了处理代理对象方法调用的方法。

    应该实现该接口并书写处理逻辑。如果希望处理时调用被代理的实例,应该维护一个成员变量以存放被代理的实例,在 invoke 方法中通过反射机制调用被代理的实例的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public interface InvocationHandler {
    /**
    * 处理代理对象方法调用
    * <p>当代理对象的方法被调用时,将通过此方法进行处理
    * @param proxy 代理对象
    * @param method 需要代理的方法
    * @param args 由方法参数所构成的数组
    */
    public Object invoke(Object proxy,
    Method method,
    Object[] args) throws Throwable;
    }

(2) 基本步骤

  • 编写调用处理者类,实现 InvocationHandler 接口,在其中书写处理逻辑

  • 实例化调用处理者类,获取调用处理者

  • 如果希望处理时调用被代理的实例,应该实例化调用处理者类并放入调用处理者

  • 获取代理对象

  • 调用代理对象的方法

    当代理对象的方法被调用时,调用请求会自动转发给 InvocationHandler 对象的 invoke 方法,由 invoke 方法对请求做处理

3. 简单示例

接口:

1
2
3
interface Animal {
void bark();
}

接口实现类:

1
2
3
4
5
class Dog implements Animal {
public void bark() {
System.out.println("汪汪汪");
}
}

调用处理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ProxyInvocationHandler implements InvocationHandler{

private Object object;

public ProxyInvocationHandler(Object object) {
this.object = object;
}

public Object invoke(Object proxy,
Method method,
Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("调用前");
Object res = method.invoke(object, args);
System.out.println("调用后");
return res;
}
}

代理对象的获取及调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 被代理的实例
Animal dog = new Dog();

// 实例化调用处理类
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(dog);

// 获取代理对象
Animal proxyDog = (Animal) Proxy.newProxyInstance(dog.getClass().getClassLoader(),
dog.getClass().getInterfaces(),
proxyInvocationHandler);

// 调用代理对象
proxyDog.bark();

4. 通用调用处理类

  • 将成员变量的设为 Object 类型,使其可以适用于任意类型
  • 提供 getProxy() 方法,可以直接获取代理对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ProxyInvocationHandler implements InvocationHandler{

private Object object;

public void setObject(Object object) {
this.object = object;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ···做些什么···
Object result = method.invoke(object, args);
// ···做些什么···
return result;
}

public Object getProxy() {
return Proxy.newProxyInstance(
object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
this);
}
}

五、优缺点

1. 优点

  • 协调调用者和被调用者,降低系统耦合性

2. 缺点

  • 增加了代码量
  • 减缓了程序运行速度

参考