设计模式(十三)结构型:代理模式详解
代理模式(Proxy Pattern)是 GoF 23 种设计模式中的结构型模式之一,其核心价值在于为其他对象提供一种间接访问的机制,以控制对原始对象的访问。它通过引入一个“代理”对象,作为客户端与真实对象之间的中介,从而在不改变原始接口的前提下,实现访问控制、延迟初始化、日志记录、权限校验、缓存、远程通信等附加功能。代理模式是实现“开闭原则”和“单一职责原则”的重要手段,广泛应用于远程服务调用(RMI、Web Service)、虚拟代理(延迟加载)、保护代理(权限控制)、智能引用(资源管理)等场景,是构建安全、高效、可维护系统的关键架构模式。
一、详细介绍
代理模式解决的是“直接访问目标对象存在限制或需要增强控制”的问题。在某些情况下,客户端不能或不应直接访问真实对象,例如:
- 对象创建代价高昂(如大型图像、数据库连接),需延迟加载;
- 对象位于远程主机,需通过网络访问;
- 对象涉及敏感操作,需进行权限验证;
- 需要监控对象的访问行为(如调用次数、执行时间)。
代理模式通过一个与真实对象具有相同接口的代理对象,拦截所有对真实对象的请求,并在转发前或后执行额外逻辑。客户端通过代理与真实对象交互,整个过程对客户端透明。
该模式包含以下核心角色:
- Subject(抽象主题):定义真实对象和代理对象的公共接口,客户端通过该接口访问目标。可以是接口或抽象类。
- RealSubject(真实主题):实现
Subject
接口,是代理所代表的真实对象,包含核心业务逻辑。 - Proxy(代理类):实现
Subject
接口,持有对RealSubject
的引用。它控制对真实对象的访问,可在调用前后执行额外操作(如检查权限、缓存结果、记录日志)。
根据使用目的不同,代理模式可分为多种类型:
- 远程代理(Remote Proxy):为位于不同地址空间的对象提供本地代表,隐藏网络通信细节(如 RMI、gRPC Stub)。
- 虚拟代理(Virtual Proxy):延迟创建开销大的对象,直到真正需要时才初始化(如图片懒加载)。
- 保护代理(Protection Proxy):控制对对象的访问权限,根据角色决定是否允许操作(如管理员 vs 普通用户)。
- 智能引用(Smart Reference):在访问对象时执行额外操作,如引用计数、空指针检查、缓存结果。
- 缓存代理(Caching Proxy):缓存真实对象的操作结果,提高性能,避免重复计算或远程调用。
代理模式的关键优势:
- 增强控制:可在访问前后插入逻辑,实现横切关注点。
- 解耦客户端与真实对象:客户端不依赖具体实现,便于替换或扩展。
- 提高安全性:通过保护代理实现权限隔离。
- 优化性能:通过虚拟代理延迟加载,缓存代理减少重复操作。
与“装饰器模式”相比,代理关注访问控制,装饰器关注功能增强;代理通常不改变对象行为本质,而是控制何时、如何访问;装饰器则明确添加新功能。与“外观模式”相比,代理封装的是单个对象的访问,外观封装的是多个子系统的协作。
二、代理模式的UML表示
以下是代理模式的标准 UML 类图:
图解说明:
Subject
是统一接口,客户端通过它与真实对象或代理交互。RealSubject
是真实业务对象。Proxy
持有RealSubject
引用,在request()
中可调用preRequest()
和postRequest()
执行额外逻辑。- 客户端通过
Subject
接口调用,无法区分是代理还是真实对象。
三、一个简单的Java程序实例及其UML图
以下是一个文档管理系统中“受保护的文件访问”示例,展示如何使用保护代理控制对敏感文件的访问。
Java 程序实例
// 抽象主题:文件访问接口
interface Document {void open();void edit();
}// 真实主题:实际文档对象
class RealDocument implements Document {private String fileName;public RealDocument(String fileName) {this.fileName = fileName;loadFromDisk(); // 模拟耗时操作}private void loadFromDisk() {System.out.println("📄 正在从磁盘加载文档: " + fileName);try {Thread.sleep(1000); // 模拟加载延迟} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("✅ 文档 " + fileName + " 加载完成");}@Overridepublic void open() {System.out.println("🔓 打开文档: " + fileName);}@Overridepublic void edit() {System.out.println("✍️ 编辑文档: " + fileName);}
}// 代理类:保护代理,控制文档访问权限
class ProtectedDocumentProxy implements Document {private String fileName;private RealDocument realDocument; // 延迟初始化private String currentUser;private boolean isAdmin;public ProtectedDocumentProxy(String fileName, String currentUser, boolean isAdmin) {this.fileName = fileName;this.currentUser = currentUser;this.isAdmin = isAdmin;}@Overridepublic void open() {if (canAccess()) {// 虚拟代理:延迟加载真实对象if (realDocument == null) {realDocument = new RealDocument(fileName);}logAccess("open");realDocument.open();} else {System.out.println("❌ 用户 " + currentUser + " 无权打开文档: " + fileName);}}@Overridepublic void edit() {if (canModify()) {if (realDocument == null) {realDocument = new RealDocument(fileName);}logAccess("edit");realDocument.edit();} else {System.out.println("❌ 用户 " + currentUser + " 无权编辑文档: " + fileName);}}// 权限检查:读取权限private boolean canAccess() {return isAdmin || currentUser.equals("owner");}// 权限检查:修改权限private boolean canModify() {return isAdmin; // 只有管理员可编辑}// 访问日志private void logAccess(String operation) {System.out.println("📝 日志: 用户 [" + currentUser + "] 执行 [" + operation + "] 操作 on " + fileName);}
}// 客户端使用示例
public class ProxyPatternDemo {public static void main(String[] args) {System.out.println("🔐 文档管理系统 - 保护代理示例\n");// 创建代理对象(不立即加载真实文档)Document doc = new ProtectedDocumentProxy("财务报告.docx", "alice", false);// 普通用户尝试打开文档System.out.println("👉 用户 alice (普通用户) 尝试打开文档:");doc.open(); // 允许打开System.out.println("\n👉 用户 alice 尝试编辑文档:");doc.edit(); // 拒绝编辑System.out.println("\n" + "=".repeat(50) + "\n");// 管理员访问Document adminDoc = new ProtectedDocumentProxy("财务报告.docx", "admin", true);System.out.println("👉 用户 admin (管理员) 尝试打开并编辑文档:");adminDoc.open();adminDoc.edit();System.out.println("\n💡 说明:真实文档仅在首次访问时加载,且权限由代理控制。");}
}
实例对应的UML图(简化版)
运行说明:
ProtectedDocumentProxy
实现了Document
接口,持有文件名和用户信息。- 真实文档
RealDocument
在首次open()
或edit()
时才创建(虚拟代理特性)。 - 代理在调用前检查权限(保护代理),并记录访问日志(智能引用)。
- 客户端通过统一接口操作,无法感知代理的存在。
四、总结
特性 | 说明 |
---|---|
核心目的 | 控制对对象的访问,增强安全性与灵活性 |
实现机制 | 实现相同接口,持有真实对象引用,拦截并转发请求 |
优点 | 访问控制、延迟加载、日志监控、远程透明化、解耦客户端 |
缺点 | 增加系统复杂性、可能引入性能开销(间接调用)、需维护代理逻辑 |
适用场景 | 权限控制、远程服务、延迟初始化、缓存、资源管理、AOP |
不适用场景 | 对象简单、无需控制、性能极度敏感 |
代理模式使用建议:
- 代理类应尽量轻量,避免成为性能瓶颈。
- 可结合工厂模式或依赖注入创建代理。
- 在 Java 中,动态代理(
java.lang.reflect.Proxy
)可减少静态代理的类膨胀问题。 - Spring AOP 的
JDK Dynamic Proxy
和CGLIB
是代理模式的高级应用。
架构师洞见:
代理模式是“间接性”与“控制力”的完美结合。在现代架构中,其思想已渗透到微服务治理、API 网关、服务网格(Istio/Linkerd) 和AOP(面向切面编程) 的核心。例如,在服务网格中,Sidecar 代理拦截所有进出服务的流量,实现熔断、限流、加密;在 Spring 框架中,@Transactional
注解通过代理实现声明式事务管理;在前端,Proxy 对象用于实现响应式数据监听(如 Vue 3)。未来趋势是:代理模式将与AI 安全网关结合,智能代理可动态分析请求内容并决定是否放行;在边缘计算中,设备代理将统一管理异构设备的通信协议;在元编程和运行时增强中,代理将成为实现热更新、动态配置的核心机制。
掌握代理模式,有助于设计出安全、可控、可监控的系统。作为架构师,应在系统边界、服务入口、资源访问点主动引入代理层,将“控制逻辑”与“业务逻辑”分离。代理不仅是模式,更是系统治理的哲学——它告诉我们:真正的掌控力,来自于对“访问路径”的精心设计与智能干预。