- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
代理模式(Proxy Pattern)是 GoF 经典结构型模式之一,其核心是提供一个占位符或代理对象,由它代为控制对真实对象的访问。本文将基于 java-design-patterns 仓库中的 proxy 模块(对应韩文翻译文档 localization/ko/proxy/README.md),以"魔法师之塔"这一经典示例为主线,完整讲解代理模式的结构、代码实现、运行效果、适用场景,并结合仓库源码与测试用例深入剖析其工作原理,帮助读者掌握在 Java 项目中落地代理模式的完整方法。
概述:什么是代理模式
代理模式又称Surrogate(替身)。它的意图是:为另一个对象提供一种代理或占位符,以控制对该对象的访问。
用通俗的话说:
利用代理模式,一个类可以代表另一个类的功能,在保持对外接口不变的前提下,把访问控制、延迟加载等附加逻辑交给代理完成。
正如维基百科所述:
代理在其最一般的形式下,是一个充当其他事物接口的类。代理是一个包装器(wrapper)或代理对象(agent object),客户端调用它来访问幕后真正的服务对象。代理的使用既可以简单地把请求转发给真实对象,也可以提供额外的逻辑——例如当对真实对象的操作非常消耗资源时进行缓存,或者在调用真实对象之前检查前置条件。
在现实世界中,这种模式随处可见:门禁社区的保安就是住户的"代理"——访客来访时,保安先核验身份与权限,通过才放行,被拒绝则无法进入。这与代理模式控制对特定对象访问的机制完全一致。
示例背景:魔法师之塔
考虑一个魔法师们前去研习咒语的塔。象牙塔(IvoryTower)只能通过一个代理访问,该代理确保只有前三位魔法师能够入内。在这个例子里:
IvoryTower是真实的业务对象;WizardTowerProxy是代理对象,代表塔的功能并为其叠加了访问控制;Wizard是要访问塔的客户端实体。
该示例位于仓库的 proxy/src/main/java/com/iluwatar/proxy 目录,模块的入口类是 App.java。
代码实现:从接口到代理的完整链路
第一步:定义主题接口与真实对象
首先定义一个WizardTower接口,以及实现它的IvoryTower类:
public interface WizardTower { void enter(Wizard wizard); }@Slf4j public class IvoryTower implements WizardTower { public void enter(Wizard wizard) { LOGGER.info("{} enters the tower.", wizard); } }其中@Slf4j是 Lombok 提供的日志注解,为类自动生成LOGGER字段。真实对象 IvoryTower.java 只负责"放行并打印日志"这一核心业务,不含任何访问控制逻辑。
第二步:定义客户端实体
再来看一个简单的Wizard类:
public class Wizard { private final String name; public Wizard(String name) { this.name = name; } @Override public String toString() { return name; } }Wizard是一个不可变(immutable)的轻量实体,仅持有名字,toString()直接返回姓名,用于日志输出。完整代码见 Wizard.java。
第三步:实现代理并叠加访问控制
接下来,通过WizardTowerProxy为WizardTower增加访问控制:
@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); } } }从源码 WizardTowerProxy.java 可以看到代理的关键设计:
- 代理与真实对象实现相同的接口
WizardTower,对客户端完全透明; - 通过构造器注入真实对象
tower,持有对它的引用; - 用
NUM_WIZARDS_ALLOWED = 3作为准入上限,numWizards记录已进入人数; - 在
enter()中先做前置检查:名额未满则转发请求并计数,名额已满则直接拒绝并记录日志。
这种"先检查、后转发"的结构正是保护代理(Protection Proxy)的典型形态——它不修改真实对象的任何代码,只在自己的外层逻辑中完成权限校验。
运行场景与输出验证
在入口类 App.java 中,构造代理并让五位魔法师依次进入:
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"));程序运行结果如下:
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!前三位魔法师顺利进入象牙塔,从第四位(Green wizard)开始被代理拦截。结合日志框架,实际输出还会带上时间戳与类名,例如:
08:42:06.183 [main] INFO com.iluwatar.proxy.IvoryTower -- Red wizard enters the tower. 08:42:06.186 [main] INFO com.iluwatar.proxy.WizardTowerProxy -- Green wizard is not allowed to enter!注意观察日志的出处:进入记录来自真实对象IvoryTower,拒绝记录来自代理WizardTowerProxy——两类日志分别印证了"转发"与"拦截"两条路径都真实执行。
类图与调用时序
类图
Proxy 模式类图
上图展示了本模块的完整类结构(PlantUML 源文件见 proxy/etc/proxy.urm.puml):WizardTowerProxy与IvoryTower共同实现WizardTower接口,代理通过组合(-tower)持有真实对象引用,客户端App只面向WizardTowerProxy编程。
调用时序
时序上,客户端的每次enter(wizard)调用先到达代理,代理完成权限判定后,符合条件的调用才继续转发给IvoryTower处理,不符合的直接被代理拦截。从源码结构看,这也意味着代理可以在转发前后自由插入日志、缓存、延迟初始化等横切逻辑。
测试验证:代理行为的可观察保障
仓库为该模块提供了配套的单元测试,用内存日志捕获器验证行为:
- IvoryTowerTest.java:让四位魔法师(Gandalf、Dumbledore、Oz、Merlin)直接进入
IvoryTower,断言四条日志均为"xxx enters the tower.",说明真实对象本身不设任何门槛; - WizardTowerProxyTest.java:同样的四位魔法师改由代理放行,断言前三人进入、第四人(Merlin)被拒绝,日志总数仍为 4 条,说明代理完整接管了准入控制;
- AppTest.java 验证主入口可正常执行。
两组测试的对照恰恰证明了代理模式的核心价值:在不改动真实对象一行代码的前提下,仅通过叠加代理就改变了系统的访问行为。测试中用到的日志捕获工具是 InMemoryAppender.java,它基于 logback 的AppenderBase把日志收集到内存列表,再通过logContains()与getLogSize()做断言。
何时使用代理模式
代理适用于"需要一个比简单指针更灵活、更精细的对象引用"的场景。以下是代理模式的常见应用情境:
- 远程代理(Remote Proxy):为处于不同地址空间的对象提供本地代表,客户端通过本地代理透明地访问远端对象,例如 RMI(远程方法调用);
- 虚拟代理(Virtual Proxy):按需创建开销昂贵的对象,推迟到真正使用时才实例化,例如加载大图或复杂计算对象;
- 保护代理(Protection Proxy):控制对原始对象的访问,在对象需要按不同权限开放给不同访问者时非常有用,本示例即属此类。
除此之外,代理模式通常还被用来:
- 控制对另一个对象的访问(访问控制);
- 实现延迟初始化(lazy initialization);
- 实现日志记录(logging);
- 促进网络连接的建立与管理;
- 对对象的引用进行计数(reference counting);
- 为位于不同地址空间的对象提供本地表示。
与其他模式的关系
代理模式与仓库中的其他结构型模式既有相似又有区别:
- Ambassador 模式:代理模式的近亲,同样充当中间人,尤其适用于远程通信场景,在访问控制与监控之外还会承担连接管理与容错职责;
- 适配器模式(Adapter):适配器改变既有对象的接口,而代理对外提供与真实对象相同的接口;
- 装饰器模式(Decorator):两者都提供间接层,但装饰器是动态地为对象添加职责,代理则是控制访问;
- 门面模式(Facade):门面为复杂子系统提供简化接口,代理则针对单个特定对象做访问控制。
在仓库中的延伸阅读
如果想进一步观察"虚拟代理"(Virtual Proxy)在 java-design-patterns 中的另一种落地形态,可以阅读 virtual-proxy 模块 及其源码 virtual-proxy/src/main/java/com/iluwatar/virtual/proxy:其中VideoObjectProxy充当真实对象RealVideoObject的替身,在客户端真正需要资源时才完成昂贵对象的实例化,是延迟初始化在代理模式中的直接应用,与本文的WizardTowerProxy形成"保护代理 vs 虚拟代理"的对照。
小结
通过 java-design-patterns 仓库中的 proxy 模块,我们可以看到代理模式的完整落地路径:WizardTower接口定义契约,IvoryTower承载真实业务,WizardTowerProxy在不改动真实对象的前提下注入访问控制,App作为客户端只面向代理编程。代理模式为 Java 开发者在访问控制、延迟初始化、远程调用、日志记录等场景下提供了一种低侵入、高内聚的解耦手段——所有附加逻辑都收敛在代理类中,真实对象始终保持纯净。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
代理模式(Proxy Pattern)实战指南:基于 java-design-patterns 源码解析访问控制与懒加载
代理模式(Proxy Pattern)实战指南:基于 java design patterns 源码解析访问控制与懒加载 代理模式(Proxy Pattern,
示例工程教程java-design-patterns 中的迭代器模式(Iterator Pattern):Java 顺序访问聚合对象的实现范式
java design patterns 中的迭代器模式(Iterator Pattern):Java 顺序访问聚合对象的实现范式 本文以开源仓库 java d
示例工程教程Atlantafx 错误处理与调试:解决 JavaFX 样式问题的完整指南
Atlantafx 错误处理与调试:解决 JavaFX 样式问题的完整指南 JavaFX 样式开发中,Atlantafx 作为现代化的 CSS 主题集合,为开发
UI组件桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考