Java 23 种设计模式:从踩坑到精通 | 番外:代理模式 —— 物流服务访问控制实战
Java 23 种设计模式:从踩坑到精通 | 番外:代理模式 —— 物流服务访问控制实战
摘要:代理模式为其他对象提供一种代理以控制对这个对象的访问,可以在不修改原代码的情况下添加权限校验、日志记录、缓存加速、延迟加载等横切逻辑。本文结合**智能物流中“远程物流服务调用”**的场景,完整展示如何用代理类控制对真实物流服务的访问,并与装饰器模式深度对比,帮你掌握“控制访问”的设计精髓。
🗺️本文阅读地图(3 分钟速览)
- 为什么每次调用远程物流服务都要经过权限校验?
- ✅ 代理模式核心角色:抽象主题、真实主题、代理
- 手写物流服务代理:权限校验 + 日志 + 缓存 + 延迟加载
- 🚀 代理 vs 装饰器:控制访问 vs 增强功能
- 🎤 面试必问:“代理模式和装饰器模式有什么区别?Spring AOP 用了哪个?”
📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 正篇:代理模式 Proxy —— 你的 AOP 就是用代理实现的 |当前:番外 · 代理模式 × 物流服务访问控制
🔗 返回系列总目录
1. 物流远程服务调用的痛点
在物流系统中,真实的物流服务部署在远程服务器上,每次调用需要建立网络连接、进行权限校验、记录访问日志。如果把这些横切逻辑直接写在业务代码中,业务代码会变得臃肿且难以维护。更麻烦的是,真实物流服务的初始化成本很高(建立连接、加载配置),如果每次调用都重新初始化,会严重影响性能。
代理模式的解决思路:创建一个代理类,实现与真实物流服务相同的接口,在代理类中添加权限校验、日志、缓存等横切逻辑,并延迟加载真实服务对象。客户端只与代理交互,完全不知道代理的存在。
1.1 你的场景该不该用代理?
| 判断标准 | 是 → 用代理 | 否 → 用其他方式 |
|---|---|---|
| 需要在不修改原代码的情况下控制访问 | ✅ | ❌ |
| 需要添加横切逻辑(日志/事务/缓存/权限) | ✅ | ❌ |
| 真实对象创建成本高,需要延迟加载 | ✅ | ❌ |
| 只是想给对象增加新功能,且希望自由组合 | ❌ | 用装饰器模式 |
| 需要转换接口 | ❌ | 用适配器模式 |
2. 代理模式 UML(物流服务访问控制场景)
3. 完整源码实现
3.1 抽象主题:物流服务接口 (ShippingService)
/** * 抽象主题:物流服务接口 */publicinterfaceShippingService{doublecalculateFee(doubleweight,doubledistance);StringtrackOrder(StringorderId);}💬白话:这是客户端和代理、真实服务之间的“合同”——大家都实现这个接口,客户端只管调用,不关心背后是谁在执行。
3.2 真实主题:远程物流服务 (RealShippingService)
/** * 真实主题:真实物流服务(模拟远程服务) */publicclassRealShippingServiceimplementsShippingService{publicRealShippingService(){// 模拟连接远程服务的耗时操作System.out.println(" 🔗 正在连接远程物流服务器...");try{Thread.sleep(500);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}System.out.println(" ✅ 远程物流服务已连接\n");}@OverridepublicdoublecalculateFee(doubleweight,doubledistance){System.out.println(" 📦 [RealService] 正在计算运费...");doublebaseRate=5.0;doublefee=baseRate+weight*2.5+distance*0.8;returnMath.round(fee*100.0)/100.0;}@OverridepublicStringtrackOrder(StringorderId){System.out.println(" 📦 [RealService] 正在查询物流轨迹...");returnString.format("订单[%s]:已发货 → 运输中 → 预计2天后到达",orderId);}}💬白话:真实物流服务的初始化非常耗时——要连接远程服务器、加载配置。如果用代理延迟加载,第一次调用时才初始化,能大幅提升启动速度。
3.3 代理类:物流服务代理 (ShippingServiceProxy)
importjava.util.HashMap;importjava.util.Map;/** * 代理类:物流服务代理 * 实现权限校验、日志记录、缓存、延迟加载 */publicclassShippingServiceProxyimplementsShippingService{privateRealShippingServicerealService;// 持有真实对象的引用privateStringapiKey;privateintrequestCount;privateMap<String,Object>cache;publicShippingServiceProxy(StringapiKey){this.apiKey=apiKey;this.requestCount=0;this.cache=newHashMap<>();System.out.println(" 🛡️ 物流代理已初始化(延迟加载:尚未连接真实服务)\n");}/** * 延迟加载真实服务对象 */privateRealShippingServicegetRealService(){if(realService==null){realService=newRealShippingService();}returnrealService;}/** * 权限校验 */privatebooleancheckPermission(){if(apiKey==null||apiKey.isEmpty()){System.out.println(" ❌ 权限校验失败:API Key 为空");returnfalse;}if(!apiKey.startsWith("LOGISTICS_")){System.out.println(" ❌ 权限校验失败:无效的 API Key");returnfalse;}System.out.println(" ✅ 权限校验通过");returntrue;}/** * 记录访问日志 */privatevoidlogAccess(Stringmethod,Objectparams){requestCount++;System.out.printf(" 📝 [日志] 第%d次访问 | 方法:%s | 参数:%s\n",requestCount,method,params);}/** * 缓存结果 */privatevoidcacheResult(Stringkey,Objectvalue){cache.put(key,value);System.out.printf(" 💾 [缓存] 已缓存结果:key=%s\n",key);}/** * 从缓存获取结果 */privateObjectgetCachedResult(Stringkey){if(cache.containsKey(key)){System.out.printf(" ⚡ [缓存命中] 直接返回缓存结果:key=%s\n",key);returncache.get(key);}returnnull;}@OverridepublicdoublecalculateFee(doubleweight,doubledistance){logAccess("calculateFee",String.format("weight=%.1f, distance=%.1f",weight,distance));if(!checkPermission()){thrownewSecurityException("无权访问物流服务");}StringcacheKey="fee_"+weight+"_"+distance;Objectcached=getCachedResult(cacheKey);if(cached!=null)return(double)cached;doubleresult=getRealService().calculateFee(weight,distance);cacheResult(cacheKey,result);returnresult;}@OverridepublicStringtrackOrder(StringorderId){logAccess("trackOrder",orderId);if(!checkPermission()){thrownewSecurityException("无权访问物流服务");}StringcacheKey="track_"+orderId;Objectcached=getCachedResult(cacheKey);if(cached!=null)return(String)cached;Stringresult=getRealService().trackOrder(orderId);cacheResult(cacheKey,result);returnresult;}publicintgetRequestCount(){returnrequestCount;}}💬白话:代理类就像前台的“门禁”——先验权限、再记日志、然后看缓存有没有,最后才放行给真实服务。真实服务只初始化一次(
getRealService()),后续调用直接复用。
3.4 客户端测试
publicclassClient{publicstaticvoidmain(String[]args){System.out.println("=== 智能物流远程服务代理(代理模式)===\n");ShippingServiceshippingService=newShippingServiceProxy("LOGISTICS_12345");// 场景1:首次调用(触发真实服务连接 + 缓存)System.out.println("【场景1】首次计算运费");System.out.println(shippingService.calculateFee(10.5,150.0)+"\n");// 场景2:相同参数再次调用(缓存命中)System.out.println("【场景2】再次计算相同运费");System.out.println(shippingService.calculateFee(10.5,150.0)+"\n");// 场景3:查询物流轨迹(首次,无缓存)System.out.println("【场景3】查询物流轨迹");System.out.println(shippingService.trackOrder("ORD20260723001")+"\n");// 场景4:再次查询相同订单(缓存命中)System.out.println("【场景4】再次查询相同订单");System.out.println(shippingService.trackOrder("ORD20260723001")+"\n");// 场景5:不同参数计算运费(缓存未命中)System.out.println("【场景5】计算不同运费");System.out.println(shippingService.calculateFee(5.0,80.0)+"\n");System.out.println("总请求次数:"+((ShippingServiceProxy)shippingService).getRequestCount());}}4. 运行结果
=== 智能物流远程服务代理(代理模式)=== 🛡️ 物流代理已初始化(延迟加载:尚未连接真实服务) 【场景1】首次计算运费 📝 [日志] 第1次访问 | 方法:calculateFee | 参数:weight=10.5, distance=150.0 ✅ 权限校验通过 🔗 正在连接远程物流服务器... ✅ 远程物流服务已连接 📦 [RealService] 正在计算运费... 💾 [缓存] 已缓存结果:key=fee_10.5_150.0 151.25 【场景2】再次计算相同运费 📝 [日志] 第2次访问 | 方法:calculateFee | 参数:weight=10.5, distance=150.0 ✅ 权限校验通过 ⚡ [缓存命中] 直接返回缓存结果:key=fee_10.5_150.0 151.25 【场景3】查询物流轨迹 📝 [日志] 第3次访问 | 方法:trackOrder | 参数:ORD20260723001 ✅ 权限校验通过 📦 [RealService] 正在查询物流轨迹... 💾 [缓存] 已缓存结果:key=track_ORD20260723001 订单[ORD20260723001]:已发货 → 运输中 → 预计2天后到达 【场景4】再次查询相同订单 📝 [日志] 第4次访问 | 方法:trackOrder | 参数:ORD20260723001 ✅ 权限校验通过 ⚡ [缓存命中] 直接返回缓存结果:key=track_ORD20260723001 订单[ORD20260723001]:已发货 → 运输中 → 预计2天后到达 【场景5】计算不同运费 📝 [日志] 第5次访问 | 方法:calculateFee | 参数:weight=5.0, distance=80.0 ✅ 权限校验通过 📦 [RealService] 正在计算运费... 💾 [缓存] 已缓存结果:key=fee_5.0_80.0 81.25 总请求次数:5 次5. 核心角色回顾
| 角色 | 职责 | 对应代码 |
|---|---|---|
| Subject | 定义真实对象和代理的公共接口 | ShippingService |
| RealSubject | 真正执行业务逻辑的对象 | RealShippingService |
| Proxy | 持有真实对象引用,控制访问 | ShippingServiceProxy |
6. 代理模式 vs 装饰器模式
| 对比项 | 代理模式 | 装饰器模式 |
|---|---|---|
| 意图 | 控制访问(权限、缓存、延迟加载) | 增强功能(添加职责) |
| 真实对象的创建 | 代理自己创建(控制创建时机) | 由客户端创建并注入 |
| 客户端感知 | 客户端通常不知道代理的存在 | 客户端显式组合装饰器 |
| 嵌套能力 | 通常一对一 | 可层层嵌套,任意组合 |
| 典型应用 | Spring AOP、MyBatis Mapper、远程代理 | Java I/O 流、Collections.synchronizedList() |
💡一句话记忆:代理是“中介”——控访问、管创建;装饰器是“加料师”——加功能、可套娃。Spring AOP 用代理控制事务、权限,Java I/O 用装饰器给流加缓冲、加密。
7. 代理模式的优缺点
| 优点 | 缺点 |
|---|---|
| 职责分离:横切逻辑与业务逻辑解耦 | 代理类增加,系统复杂度上升 |
| 延迟加载:避免初始化大对象 | 动态代理(反射)有一定性能损耗 |
| 符合开闭原则:扩展无需修改原类 | JDK 动态代理必须基于接口 |
8. 六大设计原则体现
| 原则 | 体现 |
|---|---|
| 单一职责 | 代理负责横切控制,真实服务负责业务 |
| 开闭原则 | 新增缓存、权限等能力通过代理扩展 |
| 里氏替换 | 代理与真实对象实现同一接口 |
| 依赖倒置 | 客户端依赖抽象ShippingService接口 |
| 接口隔离 | 抽象接口方法精简 |
| 迪米特法则 | 客户端只与代理交互,不知真实对象存在 |
附 代理模式 UML源码(物流服务访问控制场景)
@startuml title Java 23 种设计模式:从踩坑到精通 footer 折哥 | 智能物流与Java实战 ' 1. 全局样式配置 skinparam backgroundColor #FEFEFE skinparam shadowing false skinparam classBorderColor #333333 skinparam classFontColor #1A1A1A skinparam classFontSize 14 skinparam noteFontSize 12 skinparam noteFontColor #555555 skinparam arrowColor #555555 skinparam classBackgroundColor #F9F9F9 skinparam interface { BackgroundColor #E8F5E9 BorderColor #2E7D32 } ' 2. 抽象主题 interface ShippingService { + calculateFee(weight, distance) : double + trackOrder(orderId) : String } note right of ShippingService <b>抽象主题(物流接口)</b> -- 定义真实对象和代理对象的公共接口 客户端通过此接口访问 end note ' 3. 真实主题 class RealShippingService implements ShippingService { + calculateFee(weight, distance) : double + trackOrder(orderId) : String } note right of RealShippingService <b>真实主题(真实物流服务)</b> -- 真正执行业务逻辑 包含核心计算和查询功能 end note ' 4. 代理类 class ShippingServiceProxy implements ShippingService { - realService : RealShippingService - apiKey : String - requestCount : int + calculateFee(weight, distance) : double + trackOrder(orderId) : String - logAccess() - checkPermission() - cacheResult(key, value) } note right of ShippingServiceProxy <b>代理(物流服务代理)</b> -- 控制对真实对象的访问 可添加权限校验、日志、缓存等 延迟加载真实对象(懒加载) end note ' 5. 关系连线 ShippingService <|.. RealShippingService : 实现 ShippingService <|.. ShippingServiceProxy : 实现 ShippingServiceProxy o--> RealShippingService : 持有引用 @enduml🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 正篇:代理模式 Proxy —— 你的 AOP 就是用代理实现的
- 当前:番外 · 代理模式 × 物流服务访问控制(你在这里)
- 创建型模式汇总
- 结构型模式汇总
- 行为型模式汇总
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通,思路可鉴。