news 2026/10/2 5:27:30

代理模式(Proxy Pattern)实战指南:基于 java-design-patterns 源码解析访问控制与懒加载

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
代理模式(Proxy Pattern)实战指南:基于 java-design-patterns 源码解析访问控制与懒加载
  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载

代理模式(Proxy Pattern,又称 Surrogate)是 Gang of Four 提出的结构型设计模式之一,其核心是为另一个对象提供替身或占位符,从而在不改动真实对象代码的前提下控制对它的访问。本指南以当前仓库 java-design-patterns 中的 proxy 模块(原文档为 localization/es/proxy/README.md)为主线,结合模块源码与测试用例,完整讲解代理模式的意图、类结构、可复现的代码示例,以及远程代理、虚拟代理、保护代理三种典型变体的适用场景。读完本文,你将能够在自己的 Java 项目中实现一个带访问控制、懒加载或日志增强的代理层,并理解它与 Adapter、Decorator、Facade、Ambassador 等模式的边界。

模式的意图:为什么需要代理

按照原文档的定义,代理模式的**目的(Propósito)**是:

提供另一个对象的替代品(sustituto)或占位符(marcador de posición),以控制对该对象的访问。

用一句简单的话概括就是:用一个类来代表另一个类的功能,客户端与真实对象之间多了一层可控的中介。维基百科对代理的经典描述(原文档引用)是:

代理,在其最一般的形式上,是一个充当某事物接口的类。代理是一个包装对象或代理对象,由客户端调用它以访问幕后的真实服务对象。代理的用途可以仅仅是转发到真实对象,也可以提供额外的逻辑——例如在真实对象上的操作非常消耗资源时提供缓存,或在真实对象上的操作被调用之前检查前置条件。

这恰好点出了代理模式的两大价值:透明地转发,以及在不修改真实对象代码的前提下附加横切逻辑。

真实世界案例:象牙塔的守门人

原文档给出了一个生动的现实隐喻:

想象一座塔,当地的巫师们前来学习咒语。象牙塔只能通过一个代理进入,该代理确保只有前三位巫师能够入塔。在这里,代理代表了塔的功能,并为其附加了访问控制。

类似的现实对应物还包括小区门卫:访客到来时,门卫先核验其凭证与权限,再决定是否放行。无论场景如何,本质都一样——对真实对象(居民/塔)的访问必须经过一个检查关卡(门卫/代理)。

模式结构:四个参与者的职责

从 proxy/etc/proxy.urm.puml 的类图定义和 proxy/src/main/java/com/iluwatar/proxy 的源码可以看出,本示例由四个参与者构成:

参与者本示例中的类职责
抽象主题(Subject)WizardTower定义真实对象与代理共同实现的业务接口
真实主题(RealSubject)IvoryTower真正执行业务逻辑的对象,即“被代理的对象”
代理(Proxy)WizardTowerProxy持有真实主题的引用,在调用前后附加控制逻辑
客户端(Client)App只面向抽象主题编程,感知不到代理的存在

类图中明确标示了三个关键关系:WizardTowerProxy聚合(-->)持有WizardTower类型的tower引用;IvoryTower实现(..|>)WizardTower;WizardTowerProxy同样实现WizardTower。正是因为代理与真实对象实现同一接口,客户端才能无差别地调用二者。

程序化示例:完整可运行的代码

以下代码均可在 proxy/src/main/java/com/iluwatar/proxy 中找到原文实现。

第一步:定义抽象主题接口

WizardTower.java 定义了业务契约:

public interface WizardTower { void enter(Wizard wizard); }

第二步:实现真实主题 IvoryTower

IvoryTower.java 使用 Lombok 的@Slf4j注解生成日志器,真实对象本身不包含任何访问控制逻辑,它只负责“放行并记录”:

@Slf4j public class IvoryTower implements WizardTower { public void enter(Wizard wizard) { LOGGER.info("{} enters the tower.", wizard); } }

第三步:定义被传入的对象 Wizard

Wizard.java 是一个不可变的简单数据类,通过@RequiredArgsConstructor自动生成以name为参数的构造器,并重写toString()以输出巫师姓名:

@RequiredArgsConstructor public class Wizard { private final String name; @Override public String toString() { return name; } }

第四步:实现代理 WizardTowerProxy

WizardTowerProxy.java 是本模式的核心。它在构造时接收一个WizardTower真实对象,并在enter方法中附加访问控制:只有前NUM_WIZARDS_ALLOWED(常量值 3)位巫师可以入塔,其余被拒绝:

@Slf4j public class WizardTowerProxy implements WizardTower { private static final int NUM_WIZARDS_ALLOWED = 3; private int numWizards; private final WizardTower tower; public WizardTowerProxy(WizardTower tower) { this.tower = tower; } @Override public void enter(Wizard wizard) { if (numWizards < NUM_WIZARDS_ALLOWED) { tower.enter(wizard); numWizards++; } else { LOGGER.info("{} is not allowed to enter!", wizard); } } }

注意这里的关键设计:代理与真实对象实现同一接口,代理内部保存接口类型的tower引用(而非具体类),因此代理可以在不改动IvoryTower任何代码的情况下,对任意实现WizardTower的对象施加控制,这正是“开闭原则”的体现。

第五步:客户端调用场景

App.java 的main方法演示了完整的入塔场景——客户端通过代理(而非直接使用IvoryTower)依次让五位巫师尝试入塔:

public static void main(String[] args) { var proxy = new WizardTowerProxy(new IvoryTower()); proxy.enter(new Wizard("Red wizard")); proxy.enter(new Wizard("White wizard")); proxy.enter(new Wizard("Black wizard")); proxy.enter(new Wizard("Green wizard")); proxy.enter(new Wizard("Brown wizard")); }

程序输出

运行 App 后,日志输出如下(时间戳与日志器名按实际运行环境略有差异):

Red wizard enters the tower. White wizard enters the tower. Black wizard enters the tower. Green wizard is not allowed to enter! Brown wizard is not allowed to enter!

前三位巫师顺利入塔,第四、第五位被代理拦截——真实对象IvoryTower从未被修改,但访问行为已被代理完全控制。

源码级佐证:测试如何验证代理行为

仓库为代理模块提供了完整的单元测试,可作为行为契约的权威证据:

  • WizardTowerProxyTest.java 验证代理的访问控制:让 Gandalf、Dumbledore、Oz、Merlin 四位巫师依次入塔,断言前三位产生"enters the tower."日志,Merlin 产生"is not allowed to enter!"日志,且日志总数为 4。这直接印证了NUM_WIZARDS_ALLOWED = 3的计数逻辑。
  • IvoryTowerTest.java 验证真实对象不设防:同样四位巫师直接调用IvoryTower.enter,四人全部产生入塔日志——两相对照,可以直观看出“限制”完全来自代理层。
  • AppTest.java 验证入口程序可无异常执行。
  • 测试基础设施 InMemoryAppender.java 是一个基于 LogbackAppenderBase的内存日志收集器,将日志事件存入LinkedList,为上述断言提供支撑。

适用场景:何时使用代理模式

原文档明确:只要需要比简单指针更灵活、更复杂的对象引用,代理模式就适用。典型场景包括:

  1. 远程代理(Remote Proxy):为位于不同地址空间的对象提供本地代表(如 Java RMI)。
  2. 虚拟代理(Virtual Proxy):按需创建开销昂贵的对象(如大图加载、复杂计算结果的延迟实例化)。
  3. 保护代理(Protection Proxy):控制对原始对象的访问,当不同对象需要不同访问权限时尤其有用。

此外,代理模式通常还被用于:

  • 控制对另一个对象的访问
  • 延迟初始化(Lazy Initialization)
  • 实现日志记录(Logging)
  • 简化网络连接
  • 统计对象引用次数

优点与权衡

优点:

  • 受控访问:代理可以在调用真实对象之前执行检查、日志或其他附加操作,而无需侵入真实对象的代码。
  • 延迟初始化:代理可将资源密集型对象的创建推迟到真正需要时,改善启动性能与内存占用。
  • 远程透明:代理封装网络通信细节,让客户端像调用本地对象一样操作远程对象。

权衡:

  • 额外开销:增加一层代理会引入额外的间接层,可能带来微小的性能开销。
  • 复杂度上升:系统中新增类会提高结构复杂度,需要权衡是否确有引入代理的必要。

与相关模式的边界

  • Ambassador:与代理相似,同为客户端与真实服务之间的中介,特别适用于远程通信场景,并会附加访问控制与监控能力。
  • Adapter:Adapter 改变现有对象的接口(接口转换),而 Proxy 与真实对象保持同一接口(接口不变,只控制访问)。
  • Decorator:两者都提供间接层,但 Decorator 是动态地为对象添加职责(增强功能),Proxy 则是控制访问。
  • Facade:Facade 为复杂子系统提供简化接口,Proxy 则针对单个对象控制访问。

现实世界中的代理应用

从本仓库文档可以梳理出代理模式在业界与 JDK 中的常见落点(均为原文档列举,此处仅列名称便于本地检索):Java 标准库的动态代理工具 java.lang.reflect.Proxy(JDK 反射代理)、Apache Commons Proxy 项目,以及 Mockito、PowerMock、EasyMock 等 Mock 框架——它们正是利用代理机制为测试对象生成替身;iOS 的 UIAppearance 也运用了类似思想。理解手写代理的实现原理,是理解这些框架内部机制的基石。

如何在本地运行本示例

  1. 进入仓库根目录(本模块位于proxy/,其构建配置见 proxy/pom.xml)。
  2. 该模块的pom.xml声明了slf4j-api、logback-classic运行时依赖,以及junit-jupiter-engine、mockito-core测试依赖;并通过maven-assembly-plugin将主类配置为com.iluwatar.proxy.App,便于打包运行。
  3. 在仓库根目录执行测试:./mvnw -pl proxy test(需要本机已安装 JDK 与 Maven,且具备网络以拉取依赖)。
  4. 执行示例入口:./mvnw -pl proxy exec:java或在 IDE 中直接运行com.iluwatar.proxy.App,即可看到前三位巫师入塔、后两位被拒绝的日志输出。

小结

代理模式通过一个“同接口的替身对象”在客户端与真实对象之间建立可控的访问通道。本文以 java-design-patterns 仓库的proxy模块为完整蓝本,从接口、真实对象、代理到客户端调用给出了可直接运行的代码,并用测试用例验证了行为契约。掌握代理模式后,你可以将其用于权限控制、懒加载、日志审计、远程调用封装等场景,同时也为理解 JDK 动态代理与 Mock 框架的底层机制打下基础。若需进一步了解模式间的组合使用,可继续阅读仓库中的 Ambassador、Adapter 与 Decorator 模块文档。

  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 5:26:42

HER实战:用事后经验回放解决强化学习稀疏奖励难题

“hindsight”第一次出现在我面前&#xff0c;是当初调试机械臂推积木实验的时候。那个智能体磨蹭了几个小时&#xff0c;reward始终纹丝不动&#xff0c;命中目标次数为零&#xff0c;训练曲线像一条死线。后来我换了HER&#xff08;Hindsight Experience Replay&#xff0c;事…

作者头像 李华
网站建设 2026/10/2 5:26:20

VSCode Remote-SSH连接树莓派:远程开发配置实战指南

做嵌入式开发或者折腾树莓派的人&#xff0c;一定都经历过这样的尴尬&#xff1a;把SD卡拔下来插到电脑上改文件&#xff0c;改完再插回派上&#xff0c;来回折腾半天&#xff0c;效率极低。如果树莓派连着显示器&#xff0c;体验还能好一点&#xff0c;但真正干活的时候&#…

作者头像 李华
网站建设 2026/10/2 5:24:44

OpenShell:模块化与跨平台兼得的 Shell 配置管理方案

第一次听说 OpenShell 的时候&#xff0c;我还以为它又是个 zsh 主题收集器。毕竟这类项目太多了&#xff0c;号称“开箱即用”&#xff0c;实际就是把各种 oh-my-zsh 插件往一起堆&#xff0c;换台机器就到处报错。后来我把 OpenShell 的整套配置仓库拉下来跑了一遍&#xff0…

作者头像 李华
网站建设 2026/10/2 5:23:54

Dify开源LLM应用开发平台:从模型接入到RAG工作流编排实战

做了快十年的AI应用开发&#xff0c;工具链换了好几轮&#xff0c;最让我感慨的是&#xff1a;大部分看似有创意的项目&#xff0c;最后都死在了重复造轮子上。尤其是LLM应用&#xff0c;表面上看就是“调接口拼Prompt”&#xff0c;真正动手做才知道&#xff0c;模型接入、上下…

作者头像 李华
网站建设 2026/10/2 5:22:29

hindsight深度解读:从HER到日志回溯与团队复盘

hindsight&#xff0c;英文直译是“后见之明”&#xff0c;在很多场合这个词甚至带着点贬义——事都过去了&#xff0c;你才说“我早就知道会这样”。但在技术圈里&#xff0c;我越来越觉得这个词值得被正名&#xff1a;机器学习里有Hindsight Experience Replay&#xff0c;工…

作者头像 李华
网站建设 2026/10/2 5:21:37

多智能体辩论驱动的A股投研AI:从对抗到可解释裁决

这个项目本质上干了一件事&#xff1a;把A股个股分析从“问AI要一个结论”改成了“让AI做一场多空辩论&#xff0c;再出裁决报告”。这套系统跑起来之后&#xff0c;身边几个做投研的朋友都跑来问我要思路&#xff0c;原因很简单——市面上能直接生成“看多/看空/中性”结论的A…

作者头像 李华