Featured image of post 代理模式

代理模式

📖 介绍

代理模式是一种结构型模式

代理模式是为了方便访问某些资源,使对象类更加易用。为其他对象提供一种代理控制这个对象的访问。

常见的场景:

  • 🔖 买火车票不一定要去火车站,也可以去代售点。

  • 🔖 Windows 中的快捷方式,Linux 中的软链接。

  • 🔖 Spring AOP。

  • 🔖 常见的 ORM 框架,只要定义相关的接口,而不需要写实现类,就可以对 XML 或注解中的 SQL 语句进行增删改查操作。

  • 🔖 一些中间件如 RPC 框架,在拿到 jar 包中对接口的描述后,中间件会字服务启动时,生成对应的代理类。当调用接口时,实际上是通过代理类发出的 Socket 信息。

🎈 实现方式

  • 🎗️ 方法(1):抽取接口,代理对象和服务对象实现同一个接口。

  • 🎗️ 方法(2):并不是所有情况都能抽取接口(如代码改动很大、不能修改服务类),可以将代理类作为服务类的子类,让代理类继承服务类的所有接口。

  • 🎗️ 创建代理类,代理类中必须包含一个服务类的成员变量,负责服务类的整个生命周期。(可通过构造函数传入,或在内部创建)

  • 🎗️ 根据需求实现代理方法。大部分情况下,代理方法会调用服务对象的同名方法。

🚦 可考虑设计选项:

  • 构建一个方法,用于客户端判断获得的是代理对象,还是实际服务对象。

  • 代理对象中服务对象可以考虑延迟初始化。

☕ Java 中的动态代理

在 Java 标准库中,提供了动态代理功能,允许在运行期动态创建一个接口的实例。其实就是 JVM 在运行期间,动态创建 class 字节码并加载的过程。

动态代理通过 Proxy 创建代理对象,然后将接口方法“代理”给 InvocationHandler

1
2
3
4
5
6
public interface InvocationHandler {
    // Object proxy -> 代理的对象
    // Method method -> 正在调用的方法
    // Object[] args -> 方法的参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
1
2
// Proxy
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { }

在运行期间创建一个 interface 实例的步骤:

  • 定义一个 InvocationHandler 实例,负责实现接口的方法调用

  • 通过 Proxy.newProxyInstance() 创建 interface 实例

    • ClassLoader loader 类加载器(如当前类的类加载器:CurrentProxy.class.getClassLoader()
    • Class<?>[] interfaces 需要实现的接口的数组(代理的对象是实现了哪个接口)
    • InvocationHandler h 用来处理接口方法调用的 InvocationHandler 实例(生成的代理对象中的代理方法,需要做什么)
  • 返回一个 Object,可强制转型为接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 模拟经纪人代理歌手的场景

// 接口
interface Star {
    // ...
}

class class Eason implements Star {  // 需要被代理的类
    // ...
}

public class ProxyDemo {
    public static Star createProxy(Star star) {  // 创建代理对象
        Star starProxy = (Star) Proxy.newProxyInstance(
            ProxyDemo.class.getClassLoader(),
            new Class[]{Star.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  // 代理方法需要做的事情
                    // 排除 Object 中的通用方法,不进行代理
                    if (Object.class.equals(method.getDeclaringClass())) {
                        return method.invoke(this, args);
                    }
                    String methodName = method.getName();
                    if (methodName.equals("sing")) {
                        // do ...
                        return method.invoke(star, args);
                    } else if (methodName.equals("dance")) {
                        // ...
                    } else {
                        // ...
                    }
                    return null;
                }
            }
        );
        return starProxy;
    }
}

🎲 适合场景

  • 🎨 延迟初始化。将对象的初始化延迟到真正有需要的时候。

  • 🎨 访问控制。代理可仅在客户端凭据满足要求时将请求传递给服务对象。

  • 🎨 本地执行远程服务 (远程代理)。适用于服务对象位于远程服务器上的情形。代理通过网络传递客户端请求,负责处理所有与网络相关的复杂细节。

  • 🎨 记录日志请求(日志记录代理)。如需要保存对于服务对象的请求历史记录时。

  • 🎨 缓存请求结果(缓存代理)。 如可对重复请求所需的相同结果进行缓存。

🔗 参考