代理模式
约 1965 字大约 7 分钟
设计模式结构型模式
2025-06-20
代理模式是结构型模式中非常重要且应用广泛的一个。它的核心思想是为其他对象提供一种代理以控制对这个对象的访问。
一、 什么是代理模式?
1. 核心思想
在不直接访问目标对象(也叫真实对象或被代理对象)的前提下,通过一个“代理者”来间接地访问它。这个代理者可以在客户端和目标对象之间起到中介和保护的作用。
客户端以为自己直接与目标对象交互,但实际上它所有的请求都先经过了代理对象。代理对象在将请求转发给目标对象之前或之后,可以执行一些额外的操作。
2. 通俗比喻:明星和经纪人
这是解释代理模式最贴切的比喻。
- 客户端 (Client): 粉丝、广告商、记者。
- 目标对象/真实主题 (Real Subject): 明星本人 (
Star)。 - 代理对象 (Proxy): 经纪人 (
Agent)。 - 共同接口 (Subject): 明星和经纪人对外都声称能处理演艺事务(如签约、演出)。
流程:
- 广告商(客户端)想找某位明星(目标对象)拍广告。
- 他不会直接去找明星,而是联系这位明星的经纪人(代理对象)。
- 经纪人会处理一系列前置工作:
- 访问控制: 检查广告商的资质和报价,过滤掉不靠谱的请求。
- 增强服务: 协商合同条款、安排档期。
- 如果一切合适,经纪人才会去联系明星,安排具体的拍摄事宜(将请求转发给目标对象)。
- 拍摄完成后,经纪人可能还会有一些收尾工作,比如收款。
在这个过程中,经纪人(代理)控制了对明星(目标对象)的访问,并增强了服务。
二、 代理模式的结构与实现
代理模式通常包含三个角色:
- Subject(抽象主题): 定义了真实主题和代理主题的共同接口。这样,任何使用真实主题的地方都可以用代理主题来替代。
- RealSubject(真实主题): 真正执行业务逻辑的类,是代理对象所代表的真实对象。
- Proxy(代理): 持有对真实主题的引用。它实现了与真实主题相同的接口,可以在将请求转发给真实主题前后执行附加操作。
代码实例(静态代理)
场景:我们有一个提供数据库查询服务的类 DatabaseQueryService,我们想在执行查询前后记录日志。
// 1. Subject (抽象主题)
interface IQueryService {
String doQuery(String query);
}
// 2. RealSubject (真实主题)
class DatabaseQueryService implements IQueryService {
@Override
public String doQuery(String query) {
// 模拟数据库查询
System.out.println("Executing query: " + query);
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Query Result";
}
}
// 3. Proxy (代理)
class QueryServiceProxy implements IQueryService {
// 持有对真实主题的引用
private IQueryService realService;
public QueryServiceProxy() {
// 在代理中创建或获取真实对象
this.realService = new DatabaseQueryService();
}
@Override
public String doQuery(String query) {
// 在调用真实方法前执行的附加操作
System.out.println("LOG: A query is about to be executed at " + System.currentTimeMillis());
// 将请求转发给真实主题
String result = realService.doQuery(query);
// 在调用真实方法后执行的附加操作
System.out.println("LOG: The query has been executed.");
return result;
}
}
// 客户端调用
public class Client {
public static void main(String[] args) {
// 客户端直接与代理对象交互,而不知道真实对象的存在
IQueryService queryService = new QueryServiceProxy();
String result = queryService.doQuery("SELECT * FROM users");
System.out.println("Client received: " + result);
}
}客户端完全不知道日志记录的存在,它只与 IQueryService 接口交互。代理对象 QueryServiceProxy 透明地增加了日志功能。
三、 代理模式的分类
根据代理的创建时机和实现方式,代理模式主要分为以下几种:
1. 静态代理 (Static Proxy)
- 实现: 如上例所示,代理类和真实主题类都实现了同一个接口,代理类是在编译时就已经创建好的。
- 优点: 实现简单,易于理解。
- 缺点: 非常不灵活。如果接口中增加了新的方法,真实主题和所有代理类都必须进行修改。而且每代理一个真实主题,就需要创建一个新的代理类,导致类的数量剧增。
2. 动态代理 (Dynamic Proxy)
- 实现: 代理类是在运行时动态生成的,而不是在编译时。它不需要为每个真实主题手动编写代理类。
- 优点: 非常灵活。可以为一个或多个接口动态地生成代理类,解决了静态代理类数量爆炸的问题。
- 缺点: 实现相对复杂,需要借助特定的API。
- Java中的实现方式:
- JDK动态代理: 基于接口实现。要求被代理的类必须实现一个或多个接口。通过
java.lang.reflect.Proxy类和InvocationHandler接口实现。 - CGLIB动态代理: 基于继承实现。它通过生成被代理类的子类来作为代理。即使被代理的类没有实现接口,也可以使用。但不能代理
final类。
- JDK动态代理: 基于接口实现。要求被代理的类必须实现一个或多个接口。通过
3. 其他常见分类(按用途)
- 远程代理 (Remote Proxy): 代表一个位于不同地址空间(如另一台服务器)的对象。客户端调用远程代理的方法,代理负责网络通信,将请求打包发送给远程的真实对象。RPC(远程过程调用)框架的核心就是远程代理。
- 虚拟代理 (Virtual Proxy): 用于延迟加载。当一个对象的创建成本很高时(如加载一张高清大图),可以先用一个轻量级的虚拟代理来代替它。只有当客户端真正需要使用这个对象时,虚拟代理才会去创建并加载真实的重量级对象。
- 保护代理 (Protection Proxy): 用于控制对真实对象的访问权限。在将请求转发给真实对象之前,保护代理会检查调用者是否具有足够的操作权限。
- 缓存代理 (Caching Proxy): 为开销大的操作结果提供临时存储。当多个客户端请求相同的结果时,代理可以返回缓存的数据,而无需重复调用真实对象。
四、 优缺点与适用场景
优点
- 职责清晰: 真实主题只关心核心业务逻辑,代理则负责处理非核心的附加任务,符合单一职责原则。
- 高扩展性: 可以在不修改真实主题的情况下,轻松地增加新的功能(如日志、缓存、权限控制)。
- 控制访问: 代理可以作为一道屏障,对访问进行过滤和控制。
- 降低耦合: 客户端与真实主题解耦,只依赖于抽象接口。
缺点
- 增加系统复杂度: 需要额外增加代理类,可能会导致系统中的类数量增加。
- 可能降低性能: 由于在客户端和真实主题之间增加了一层代理,请求的处理速度可能会有轻微的下降。
适用场景
- 当你希望在不改变一个已有类的情况下,为其增加一些辅助功能时(如日志、事务、权限)。
- 当你需要控制对一个对象的访问时(保护代理)。
- 当你需要为一个远程对象提供一个本地代表时(远程代理)。
- 当你需要延迟加载一个重量级对象时(虚拟代理)。
- AOP(面向切面编程) 的核心实现机制就是动态代理。Spring框架中的事务管理、日志记录等功能大量使用了代理模式。
