代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它为另一个对象提供一个代理以控制对这个对象的访问。这种模式通过引入代理类来间接操作真实对象,从而在不修改原始对象的情况下为其增加额外功能或进行访问控制。

在代理模式中,通常存在以下几个关键角色:
- 抽象主题(Subject)接口: 定义了真实主题和代理主题共同的方法,这样客户端就可以一致地对待它们两者。
- 真实主题(RealSubject): 实现了抽象主题接口的具体类,代表了实际需要被代理的对象。
- 代理主题(ProxySubject): 也实现了抽象主题接口,内部持有一个对真实主题对象的引用,并在接收到客户端请求时转发给真实主题对象处理。 在转发请求前后,代理可以添加额外的业务逻辑,如权限检查、日志记录、缓存机制、延迟加载、计算耗时等。
分类
代理模式根据实现方式的不同,主要分为以下两种类型:
静态代理: 在编译期间就已经确定了代理类,代理类与真实主题类之间存在着静态的关联关系,代理类通常是手动创建的,且需要针对每个具体的真实主题类编写对应的代理类。
动态代理: 在运行时动态生成代理类,例如Java中的JDK动态代理或者CGLIB库提供的代理机制,可以根据接口或者类动态创建代理对象,无需预先知道真实主题的具体类型。
代码实现
静态代理
抽象主题
抽象主题中只有一个方法,这个方法是真实主题和代理主题都需要实现的。
public interface Subject {
void request();
}真实主题
真实主题实现了接口的抽象行为。
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject request");
}
}代理主题
代理主题实现了接口的抽象行为,并且持有真实主题的引用。代理主题在接收到客户端请求后,可以选择在真实主题之前或之后添加额外的业务逻辑。
public class ProxySubject implements Subject{
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
beforeRequest();
// 调用真实主题的方法
this.realSubject.request();
afterRequest();
}
private void beforeRequest() {
System.out.println("Proxy: Before the request.");
}
private void afterRequest() {
System.out.println("Proxy: After the request.");
}
}输出结果中可以看到,通过代理类我们在执行request方法前后,加上了日志。

动态代理
动态代理的实现过程如下:
- 定义一个接口,这个接口需要被代理的类实现。
- 使用JDK的动态代理类
Proxy生成一个代理对象,这个代理对象实现了接口。 - 通过代理对象调用真实对象的方法。
这里使用jdk中的InvocationHandler接口,重写invoke方法,在invoke方法中添加额外的业务逻辑。

public class ProxyInvocationHandler implements InvocationHandler {
private final Subject subject;
public ProxyInvocationHandler(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeRequest();
Object result = method.invoke(this.subject, args);
afterRequest();
return result;
}
private void beforeRequest() {
System.out.println("Dynamic Proxy: Before the request.");
}
private void afterRequest() {
System.out.println("Dynamic Proxy: After the request.");
}
}InvocationHandler
java.lang.reflect.InvocationHandler是一个接口,它是Java动态代理机制中的核心组件之一。Java的动态代理允许开发者在运行时创建和修改类的行为,而无需硬编码这些行为到实际的类中。
当使用Proxy.newProxyInstance()方法创建一个动态代理对象时,需要提供一个实现了InvocationHandler接口的类实例作为调用处理器。这个调用处理器将在代理对象的方法被调用时起到关键作用。
InvocationHandler接口定义了一个方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;- proxy: 这是当前正在处理方法调用的代理对象引用。
- method: 代表了将要被调用的、位于代理接口上的具体方法。
- args: 包含了调用该方法时传递的实际参数值数组。
当客户端通过代理对象调用方法时,实际上是调用了此invoke方法。在此方法内部,开发者可以实现对原始方法调用前后的各种拦截操作,如权限检查、日志记录、数据预处理或后处理等,并最终决定是否以及如何执行原始方法。
在Client中使用动态代理:
public class Client {
public static void main(String[] args) throws Throwable {
RealSubject realSubject = new RealSubject();
ProxyInvocationHandler handler = new ProxyInvocationHandler(realSubject);
Subject subject = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(), handler);
subject.request();
}
}执行结果为:

与其他设计模式的对比
| 设计模式 | 主要功能 | 实现方式 | 适用场景 |
|---|---|---|---|
| 代理模式 | 控制对象访问 | 组合 | 需要控制访问权限时 |
| 装饰器模式 | 动态扩展对象功能 | 组合 + 继承 | 需要动态添加/删除功能时 |
| 适配器模式 | 接口转换 | 继承或组合 | 接口不兼容时 |
| 外观模式 | 简化接口 | 组合 | 需要简化复杂系统时 |
总结
核心思想
代理模式通过引入一个代理对象来间接控制对真实对象的访问,在不修改原始对象的情况下,可以为其增加额外功能或进行访问控制。
主要角色
- 抽象主题(Subject):定义真实主题和代理主题共同的接口
- 真实主题(RealSubject):实现抽象主题接口的具体类,代表实际需要被代理的对象
- 代理主题(ProxySubject):实现抽象主题接口,内部持有真实主题引用,负责转发请求并添加额外逻辑
对比
静态代理的特点
- 编译期间就已经确定代理类
- 代理类与真实主题类存在静态关联关系
- 需要针对每个具体的真实主题类编写对应的代理类
- 简单直观,但维护成本高
动态代理的特点
- 运行时动态生成代理类
- 无需预先知道真实主题的具体类型
- 可根据接口或类动态创建代理对象
- 如Java中的JDK动态代理或CGLIB库
优点
- 访问控制:可以在代理中实现权限检查、安全控制等
- 额外功能:可以在不修改原始类的情况下添加日志记录、性能监控等功能
- 延迟加载:可以实现对象的延迟初始化,提高系统性能
- 减少耦合:客户端无需直接与真实对象交互,降低了耦合度
缺点
- 复杂性增加:引入代理类增加了系统的复杂性
- 性能开销:代理层的存在会带来一定的性能开销
- 实现复杂度:动态代理的实现相对复杂
适用场景
- 需要控制对象访问权限时
- 需要在访问对象前后添加额外逻辑时
- 需要实现延迟加载时
- 需要对复杂系统提供简化接口时
- 需要实现远程调用或分布式服务时
实际应用
- Spring AOP中的切面实现
- MyBatis中的Mapper接口代理
- 远程方法调用(RMI)的远程代理
- 权限控制系统中的访问控制
- 日志记录、性能监控等横切关注点的实现