在软件工程中,"间接性"往往是灵活性与可维护性的源泉。代理模式(Proxy Pattern)正是这一思想的经典体现。它通过引入一个"中间人"对象,在不改变原始接口的前提下,实现对目标对象的访问控制、功能增强或行为拦截。无论是图片懒加载、权限校验、远程调用,还是 Spring 的事务管理,背后都离不开代理的身影。
本文将系统性地展开代理模式的全貌,共分 14 个章节,层层递进,覆盖理论、分类、实现、对比、框架集成、性能分析与最佳实践,助你真正掌握这一核心设计模式。
第一章:代理模式的定义与核心思想
代理模式属于结构型设计模式,其官方定义为:"为其他对象提供一种代理以控制对这个对象的访问。"
其本质是通过一个中介对象(Proxy)来代表真实对象(RealSubject),客户端只与代理交互,而代理决定何时、如何、是否将请求转发给真实对象。
核心思想包括三点:
- 透明性:客户端无需知道代理的存在,调用方式与直接调用真实对象一致。
- 控制性:代理可在请求前后插入逻辑,如权限检查、日志记录、缓存等。
- 解耦性:将横切关注点(如安全、日志)从业务逻辑中剥离,符合单一职责原则。
例如,当你点击网页中的图片时,浏览器可能先显示占位图,等滚动到可视区域才加载真实图片------这就是虚拟代理的典型应用。
第二章:代理模式的角色与 UML 结构
代理模式包含四个核心角色:
- Subject(抽象主题):定义代理与真实对象的公共接口,确保客户端可统一处理两者。
- RealSubject(真实主题):实现 Subject 接口,包含核心业务逻辑。
- Proxy(代理):也实现 Subject 接口,内部持有 RealSubject 引用,在转发请求前后执行附加操作。
- Client(客户端):仅依赖 Subject 接口,完全 unaware 代理或真实对象的具体类型。
UML 类图如下:
聚合 <<interface>> Subject +request() RealSubject +request() Proxy -realSubject: RealSubject +request()
该结构保证了开闭原则:新增代理类型无需修改客户端代码。
第三章:虚拟代理(Virtual Proxy)------延迟加载重型资源
虚拟代理用于延迟创建开销大的对象,直到真正需要时才初始化。适用于图像、视频、数据库连接池、大型计算结果等场景。
典型实现:
- 代理持有一个空引用;
- 首次调用方法时,才创建真实对象;
- 后续调用直接复用已创建实例。
优势:
- 减少内存占用;
- 加快系统启动速度;
- 提升用户体验(如网页首屏渲染更快)。
💡 浏览器中的
<img loading="lazy">就是虚拟代理的 HTML 原生实现。
第四章:保护代理(Protection Proxy)------基于角色的访问控制
保护代理用于根据用户身份或权限决定是否允许访问某操作。常见于银行系统、后台管理、API 网关等。
实现要点:
- 代理持有当前用户上下文(如角色、权限列表);
- 在调用真实方法前进行鉴权;
- 若无权限,抛出异常或返回错误信息。
示例:普通用户只能查看账户余额,管理员才能执行资金划转。通过保护代理,业务逻辑无需掺杂权限判断代码,保持纯净。
第五章:远程代理(Remote Proxy)------封装网络通信
远程代理为位于不同地址空间(如另一台机器)的对象提供本地代表,隐藏底层网络细节。
典型应用:
- Java RMI(Remote Method Invocation)
- gRPC 客户端 Stub
- Web Service 的 SOAP/REST 客户端
客户端调用代理方法如同调用本地方法,而代理负责:
- 序列化参数;
- 发起网络请求;
- 反序列化响应;
- 处理超时、重试、熔断等。
这极大简化了分布式系统的开发复杂度。
第六章:智能引用代理(Smart Reference Proxy)------自动附加行为
智能引用代理在每次访问对象时自动触发额外动作,如:
- 引用计数(用于资源回收)
- 访问日志
- 缓存命中统计
- 对象状态监控
例如,一个文件句柄代理可在每次读取时记录访问次数,并在引用归零时自动关闭文件。这种模式常用于资源管理或调试工具中。
第七章:缓存代理(Cache Proxy)------提升系统性能
缓存代理对昂贵操作的结果进行缓存,避免重复计算或 I/O。
实现方式:
- 以方法参数为 key,结果为 value 存入 Map 或 Redis;
- 调用前先查缓存,命中则直接返回;
- 未命中则委托真实对象执行,并写入缓存。
适用场景:
- 数据库查询结果
- 复杂数学计算
- 第三方 API 调用
⚠️ 注意缓存一致性问题,需结合过期策略或主动失效机制。
第八章:日志与监控代理(Logging/Monitoring Proxy)
此类代理专注于非功能性需求,如:
- 记录方法调用时间、参数、返回值;
- 统计 QPS、错误率;
- 上报指标至 Prometheus 或 ELK。
优势在于业务代码零侵入。例如,一个订单服务无需关心"谁在调用我",只需专注下单逻辑,日志由代理自动完成。
第九章:静态代理 vs 动态代理 ------ 手动编写 vs 运行时生成
| 维度 | 静态代理 | 动态代理 |
|---|---|---|
| 生成时机 | 编译期手动编写 | 运行期 JVM 动态生成 |
| 灵活性 | 低(每个接口需单独代理类) | 高(通用 InvocationHandler) |
| 性能 | 略高(无反射) | 略低(依赖反射) |
| 维护成本 | 高(代码冗余) | 低(逻辑集中) |
静态代理适合简单、明确的场景;动态代理适合框架级通用增强。
第十章:JDK 动态代理深度剖析
JDK 动态代理基于 java.lang.reflect.Proxy 和 InvocationHandler ,仅支持接口代理。
工作流程:
- 调用
Proxy.newProxyInstance(); - JVM 在运行时生成
$Proxy0类(继承 Proxy,实现指定接口); - 所有接口方法调用被转发至
InvocationHandler.invoke(); - Handler 决定是否调用
method.invoke(target, args)。
局限性:
- 无法代理没有接口的类;
- 无法代理 final 方法;
- 性能略低于直接调用(反射开销约 10--20%)。
第十一章:CGLIB 动态代理与字节码增强
CGLIB(Code Generation Library)通过继承目标类并重写方法 实现代理,支持类代理。
原理:
- 使用 ASM 操作字节码;
- 生成子类(如
UserService$$EnhancerByCGLIB); - 在子类方法中插入回调逻辑。
优势:
- 可代理无接口的类;
- 性能优于 JDK 代理(尤其在高频调用下);
- 支持方法过滤、回调链等高级特性。
Spring 默认优先使用 JDK 代理,若目标类无接口则自动切换至 CGLIB。
第十二章:Spring AOP 中的代理机制实战
Spring AOP 是代理模式最成功的工业级应用。其核心就是通过代理实现切面编程。
关键点:
- @EnableAspectJAutoProxy 开启自动代理;
- 切面(Aspect)定义通知(Advice);
- Spring 容器自动为目标 Bean 创建代理;
- 调用被代理方法时,自动织入前置、后置、环绕通知。
示例:
java
@Aspect
@Component
public class LogAspect {
@Around("@annotation(Loggable)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
System.out.println("Method took: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
}
🔥 注意:只有通过 Spring 容器调用的方法才会被代理!内部方法调用(this.method())不会触发 AOP。
第十三章:Hibernate 与 MyBatis 中的代理应用
- Hibernate :对
@OneToMany、@ManyToOne等关联属性使用虚拟代理 实现懒加载。当你访问user.getOrders()时,Hibernate 才发起 SQL 查询。 - MyBatis :Mapper 接口无实现类,MyBatis 通过JDK 动态代理为每个接口方法绑定对应的 SQL 语句。
二者都利用代理实现了"声明式编程":开发者只需定义接口或注解,框架自动完成底层逻辑。
第十四章:代理模式的最佳实践与陷阱规避
✅ 最佳实践:
- 明确代理目的:是控制访问?还是增强功能?
- 优先使用动态代理:减少样板代码,提升可维护性;
- 避免代理链过深:多层代理会增加调试难度;
- 合理管理生命周期:虚拟代理注意内存泄漏;
- 结合工厂模式:统一创建和配置代理。
❌ 常见陷阱:
- 循环依赖:代理持有真实对象,真实对象又持有代理;
- 状态不一致:代理与真实对象各自维护状态;
- 性能误判:在高频路径上滥用代理导致瓶颈;
- 忽略异常传播:代理未正确处理或包装异常。
📊 适用场景总结:
- 需要延迟加载 → 虚拟代理
- 需要权限控制 → 保护代理
- 需要远程调用 → 远程代理
- 需要统一增强 → 动态代理 + AOP
结语:代理,让系统更优雅
代理模式不仅是设计模式,更是一种架构哲学 :通过引入可控的间接层,换取系统的安全性、灵活性与可扩展性。从简单的图片懒加载,到企业级微服务治理,代理无处不在。
掌握代理模式,意味着你已具备"在不侵入核心业务的前提下,优雅地扩展系统能力"的能力------这正是高级工程师的核心竞争力之一。
下次当你面对"如何在不改代码的情况下加日志?"、"如何实现懒加载?"、"如何做权限拦截?"等问题时,请记住:代理,就在你手中。