设计模式(十三)结构型:代理模式详解
代理模式(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 类图:
implements implements has a uses <<interface>> Subject +request() RealSubject +request() Proxy -realSubject: RealSubject +request() +preRequest() +postRequest() Client -subject: Subject +doWork()
图解说明:
Subject
是统一接口,客户端通过它与真实对象或代理交互。RealSubject
是真实业务对象。Proxy
持有RealSubject
引用,在request()
中可调用preRequest()
和postRequest()
执行额外逻辑。- 客户端通过
Subject
接口调用,无法区分是代理还是真实对象。
三、一个简单的Java程序实例及其UML图
以下是一个文档管理系统中"受保护的文件访问"示例,展示如何使用保护代理控制对敏感文件的访问。
Java 程序实例
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 + " 加载完成");
}
@Override
public void open() {
System.out.println("🔓 打开文档: " + fileName);
}
@Override
public 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;
}
@Override
public void open() {
if (canAccess()) {
// 虚拟代理:延迟加载真实对象
if (realDocument == null) {
realDocument = new RealDocument(fileName);
}
logAccess("open");
realDocument.open();
} else {
System.out.println("❌ 用户 " + currentUser + " 无权打开文档: " + fileName);
}
}
@Override
public 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图(简化版)
implements implements creates on demand uses <<interface>> Document +open() +edit() RealDocument -fileName: String +RealDocument(fileName: String) +open() +edit() ProtectedDocumentProxy -fileName: String -realDocument: RealDocument -currentUser: String -isAdmin: boolean +open() +edit() +canAccess() +canModify() +logAccess(operation: String) Client +main(args: String[])
运行说明:
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 安全网关 结合,智能代理可动态分析请求内容并决定是否放行;在边缘计算 中,设备代理将统一管理异构设备的通信协议;在元编程 和运行时增强中,代理将成为实现热更新、动态配置的核心机制。
掌握代理模式,有助于设计出安全、可控、可监控 的系统。作为架构师,应在系统边界、服务入口、资源访问点主动引入代理层,将"控制逻辑"与"业务逻辑"分离。代理不仅是模式,更是系统治理的哲学------它告诉我们:真正的掌控力,来自于对"访问路径"的精心设计与智能干预。