设计模式(十二)代理模式 — 用代理控制访问,实现延迟加载、权限控制等作用

在软件工程中,灵活性与可维护性的源泉就是“间接性”往往。代理模式(Proxy Pattern)正是这一思想的经典体现。它经过引入一个“中间人”对象,在不改变原始接口的前提下,实现对目标对象的访问控制、功能增强或行为拦截。无论是图片懒加载、权限校验、远程调用,还是 Spring 的事务管理,背后都离不开代理的身影。

本文将系统性地展开代理模式的全貌,共分14 个章节,层层递进,覆盖理论、分类、实现、对比、框架集成、性能分析与最佳实践,助你真正掌握这一核心设计模式。


第一章:代理模式的定义与核心思想

代理模式属于结构型设计模式,其官方定义为:“为其他对象给予一种代理以控制对该对象的访问。”
其本质是通过一个中介对象(Proxy)来代表真实对象(RealSubject),客户端只与代理交互,而代理决定何时、如何、是否将请求转发给真实对象。

核心思想包括三点:

  1. 透明性:客户端无需知道代理的存在,调用方式与直接调用真实对象一致。
  2. 控制性:代理可在请求前后插入逻辑,如权限检查、日志记录、缓存等。
  3. 解耦性:将横切关注点(如安全、日志)从业务逻辑中剥离,符合单一职责原则。

虚拟代理的典型应用。就是例如,当你点击网页中的图片时,浏览器可能先显现占位图,等滚动到可视区域才加载真实图片——这就


第二章:代理模式的角色与 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.ProxyInvocationHandler仅拥护接口代理

工作流程:

  1. 调用 Proxy.newProxyInstance()
  2. JVM 在运行时生成 $Proxy0 类(继承 Proxy,实现指定接口);
  3. 所有接口方法调用被转发至 InvocationHandler.invoke()
  4. 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 创建代理;
  • 调用被代理方式时,自动织入前置、后置、环绕通知。

示例:

@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 语句。

二者都利用代理实现了“声明式编程”:开发者只需定义接口或注解,框架自动完成底层逻辑。


第十四章:代理模式的最佳实践与陷阱规避

✅ 最佳实践:

  1. 明确代理目的:是控制访问?还是增强功能?
  2. 优先利用动态代理:减少样板代码,提升可维护性;
  3. 避免代理链过深:多层代理会增加调试难度;
  4. 合理管理生命周期:虚拟代理注意内存泄漏;
  5. 结合工厂模式:统一创建和配置代理。

❌ 常见陷阱:

  • 循环依赖:代理持有真实对象,真实对象又持有代理;
  • 状态不一致:代理与真实对象各自维护状态;
  • 性能误判:在高频路径上滥用代理导致瓶颈;
  • 忽略异常传播:代理未正确处理或包装异常。

适用场景总结:

  • 应该延迟加载 → 虚拟代理
  • 需权限控制 → 保护代理
  • 需要远程调用 → 远程代理
  • 必须统一增强 → 动态代理 + AOP

结语:代理,让架构更优雅

代理模式不仅是设计模式,更是一种架构哲学:通过引入可控的间接层,换取系统的安全性、灵活性与可扩展性。从简单的图片懒加载,到企业级微服务治理,代理无处不在。

高级工程师的核心竞争力之一。就是掌握代理模式,意味着你已具备“在不侵入核心业务的前提下,优雅地扩展系统能力”的能力——这正

下次当你面对“如何在不改代码的情况下加日志?”、“如何实现懒加载?”、“如何做权限拦截?”等问题时,请记住:代理,就在你手中。

posted @ 2025-12-20 19:35  clnchanpin  阅读(61)  评论(0)    收藏  举报