目录
一、核心角色
二、代理模式的分类
关键补充:动态代理(Spring AOP 的底层原理)
三、核心作用(解决的问题)
四、代理模式 vs 装饰者模式
五、在无人售货柜项目中的典型应用
代理模式是结构型设计模式的一种,核心思想是通过一个 “代理对象” 来控制对 “真实对象” 的访问,代理对象可以在真实对象的调用前后添加额外逻辑,同时对外隐藏真实对象的实现细节。
简单来说,代理模式就像生活中的中介:你租房不用直接找房东,而是通过中介(代理)完成看房、签约、缴费等流程,中介还会帮你筛选房源、处理售后问题。
一、核心角色
代理模式包含 4 个核心角色,以无人售货柜项目的IoT设备通信场景为例:
抽象主题(Subject)定义真实对象和代理对象的共同接口,确保代理对象可以替代真实对象。
java
运行
// 设备通信接口(抽象主题) public interface DeviceCommunicator { // 发送指令给售货柜硬件 void sendCommand(String deviceId, String command); // 接收设备上报数据 String receiveData(String deviceId); }真实主题(Real Subject)实现抽象主题接口,是业务逻辑的实际执行者(真正和硬件交互的类)。
java
运行
// 真实设备通信类(真实主题) public class RealDeviceCommunicator implements DeviceCommunicator { @Override public void sendCommand(String deviceId, String command) { // 实际的硬件通信逻辑:如通过MQTT/串口发送指令 System.out.println("向设备[" + deviceId + "]发送指令:" + command); } @Override public String receiveData(String deviceId) { // 模拟从硬件接收传感器数据(如库存、温度) return "设备[" + deviceId + "]上报数据:库存剩余50件,温度25℃"; } }代理对象(Proxy)持有真实主题的引用,实现抽象主题接口,在调用真实对象方法时添加额外逻辑(如日志、权限校验、缓存)。
java
运行
// 设备通信代理类(代理对象) public class DeviceCommunicatorProxy implements DeviceCommunicator { // 持有真实对象的引用 private final DeviceCommunicator realCommunicator; public DeviceCommunicatorProxy() { this.realCommunicator = new RealDeviceCommunicator(); } @Override public void sendCommand(String deviceId, String command) { // 前置增强:权限校验 + 日志记录 if (!checkPermission(deviceId)) { throw new RuntimeException("无权限操作设备:" + deviceId); } System.out.println("【代理】发送指令前日志:设备ID=" + deviceId + ",指令=" + command); // 调用真实对象的方法 realCommunicator.sendCommand(deviceId, command); // 后置增强:记录指令发送结果 System.out.println("【代理】发送指令成功"); } @Override public String receiveData(String deviceId) { // 前置增强:缓存校验(避免重复请求硬件) String cacheData = getCacheData(deviceId); if (cacheData != null) { return "【缓存数据】" + cacheData; } // 调用真实对象获取数据 String realData = realCommunicator.receiveData(deviceId); // 后置增强:缓存数据 setCacheData(deviceId, realData); return "【实时数据】" + realData; } // 模拟权限校验 private boolean checkPermission(String deviceId) { return deviceId.startsWith("CABINET_"); // 仅允许操作售货柜设备 } // 模拟本地缓存 private final Map<String, String> dataCache = new HashMap<>(); private String getCacheData(String deviceId) { return dataCache.get(deviceId); } private void setCacheData(String deviceId, String data) { dataCache.put(deviceId, data); } }客户端(Client)只和代理对象交互,无需知道真实对象的存在。
java
运行
public class Client { public static void main(String[] args) { // 使用代理对象,而非直接使用RealDeviceCommunicator DeviceCommunicator proxy = new DeviceCommunicatorProxy(); // 发送指令(自动触发权限校验和日志) proxy.sendCommand("CABINET_001", "OPEN_DOOR"); // 接收数据(首次获取实时数据,后续获取缓存数据) System.out.println(proxy.receiveData("CABINET_001")); System.out.println(proxy.receiveData("CABINET_001")); } }
二、代理模式的分类
根据代理对象的创建时机和方式,可以分为 3 种常见类型,对应不同的业务场景:
| 类型 | 核心特点 | 适用场景 | 无人售货柜项目案例 |
|---|---|---|---|
| 静态代理 | 代理类在编译期就已确定,和真实类一一对应 | 业务逻辑固定、简单的场景 | 上述的DeviceCommunicatorProxy,为设备通信添加固定的权限和缓存逻辑 |
| 动态代理 | 代理类在运行期动态生成,无需手动编写代理类 | 多个类需要相同的代理逻辑(如统一日志、事务) | 为所有微服务接口添加统一的多租户数据隔离拦截:通过 JDK 动态代理,在接口调用前自动拼接租户 ID 作为查询条件 |
| 远程代理 | 代理对象和真实对象不在同一进程,通过网络通信 | 分布式系统、跨服务调用 | 售货柜云平台通过代理调用远程的支付结算服务:代理对象负责封装 HTTP 请求、处理网络异常、重试机制 |
关键补充:动态代理(Spring AOP 的底层原理)
在无人售货柜的微服务架构中,动态代理的应用远比静态代理广泛。例如,通过 Spring AOP 实现全局的接口日志、事务管理、多租户拦截,其底层就是动态代理。
以多租户数据隔离为例,通过 JDK 动态代理实现统一拦截:
java
运行
// 租户上下文(存储当前请求的租户ID) public class TenantContext { private static final ThreadLocal<String> TENANT_ID = new ThreadLocal<>(); public static void setTenantId(String tenantId) { TENANT_ID.set(tenantId); } public static String getTenantId() { return TENANT_ID.get(); } } // 动态代理处理器 public class TenantProxyHandler implements InvocationHandler { private final Object target; // 真实对象(如Service层Bean) public TenantProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强:获取租户ID,修改查询参数(如给Mapper的参数添加tenantId) String tenantId = TenantContext.getTenantId(); if (tenantId != null && args != null) { for (int i = 0; i < args.length; i++) { if (args[i] instanceof BaseQuery) { ((BaseQuery) args[i]).setTenantId(tenantId); } } } // 执行真实方法 return method.invoke(target, args); } } // 客户端使用动态代理 public class DynamicProxyClient { public static void main(String[] args) { // 真实对象:如商品服务的Service GoodsService realService = new GoodsServiceImpl(); // 生成动态代理对象 GoodsService proxyService = (GoodsService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), new TenantProxyHandler(realService) ); // 设置当前租户ID TenantContext.setTenantId("TENANT_001"); // 调用代理方法,自动添加租户ID条件 proxyService.queryGoods(new GoodsQuery()); } }三、核心作用(解决的问题)
- 功能增强在不修改真实对象代码的前提下,添加额外逻辑(如日志、缓存、权限、事务),符合开闭原则。
- 隐藏细节对客户端隐藏真实对象的实现细节(如硬件通信的协议、远程服务的地址),降低客户端的使用成本。
- 控制访问限制对真实对象的直接访问(如权限校验、限流、熔断),保护核心业务逻辑。
四、代理模式 vs 装饰者模式
很多开发者会混淆这两种模式,核心区别如下:
| 维度 | 代理模式 | 装饰者模式 |
|---|---|---|
| 核心目的 | 控制对真实对象的访问 | 给真实对象添加功能 |
| 关系 | 代理对象持有真实对象的引用,是 “控制者” | 装饰者和真实对象实现同一接口,是 “增强者” |
| 场景 | 权限校验、远程调用、缓存 | 功能扩展(如给 IO 流添加缓冲、加密功能) |
五、在无人售货柜项目中的典型应用
- IoT 设备通信层:用静态代理添加指令日志、设备权限校验、数据缓存。
- 微服务接口层:用动态代理(Spring AOP)实现多租户数据隔离、接口限流、全局异常处理。
- 远程服务调用:用远程代理(如 Feign 客户端)封装跨服务调用的网络请求、重试、降级逻辑。
- 支付结算模块:用代理模式封装不同支付渠道(微信、支付宝)的调用细节,对上层提供统一接口。