news 2026/10/2 17:28:01

java-design-patterns 中的代理模式(Proxy Pattern):用代理对象掌控访问控制与延迟初始化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
java-design-patterns 中的代理模式(Proxy Pattern):用代理对象掌控访问控制与延迟初始化
  • 示例工程
  • 教程

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

Design patterns implemented in Java

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

代理模式(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

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

相关推荐

上一篇:最完整HaE使用指南:从安装到高级规则配置全解析
下一篇:tldr 别名页机制深度解析:以 `jo`(autojump 别名)命令页为例

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

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

1.1 清印 ClearMark — 一款本地文档去水印工作台的完整设计与实现

1.1 清印 ClearMark — 一款本地文档去水印工作台的完整设计与实现系列第 1 篇 共 12 篇 这不是一篇产品软文&#xff0c;而是一名一线开发者对自己做过的一个工具系统的复盘。从产品定位、架构选型&#xff0c;到 PDF 内容流解析、扫描件像素级水印检测、OpenCV 图像修复、Py…

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

成都欧派特职业技能培训学校好不好 学员真实评价怎么样

当一只毛孩子第一次被温柔地放进洗护池&#xff0c;当一位零基础的年轻人第一次拿起美容剪&#xff0c;当一位初中毕业的孩子的家长在深夜里反复搜索孩子未来的出路在哪里——这些具体的、真实的瞬间&#xff0c;构成了宠物行业最朴素的底色&#xff0c;也构成了成都欧派特职业…

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

学习html前端笔记 26/10/1

学习网站 W3C官网&#xff0c;W3School&#xff0c;MDN必写の大纲 <!DOCTYPE html> //!DOCTYPE是H5最新标准的声明 <html lang"语言"> //en是英语&#xff0c;zh-CN是简体中文<head><meta charset"UTF-8"> //使用UTF-8编码…

作者头像 李华