代理模式是什么
为其它对象提供同一接口的替身,在调用真正实现前后插入控制逻辑:鉴权、计数、缓存、延迟初始化、远程调用。代理与真实对象实现同一接口,调用方通常无感,只多出一层间接调用。与装饰器的差别在意图——代理控制访问,装饰增强行为;两者实现上可以长得一样,靠动机区分。
基本信息
| 类型 | 虚代理(延迟加载)/ 保护代理(鉴权)/ 远程代理 / 日志审计 |
|---|---|
| 对比 | Decorator 增强行为;Proxy 控制访问 |
| 实现 | 手写静态代理,或 JDK 动态代理、CGLIB 字节码代理 |
| 语言生态 | Java 动态代理、.NET DispatchProxy、Python __getattr__、Go 接口包装 |
| 代价 | 多一层间接调用,动态代理会改变异常类型与栈信息 |
典型使用场景
- 延迟加载:大对象或远程资源首次访问时才真正初始化
- 访问控制:调用前校验身份与权限,拒绝时抛出可识别的异常
- 远程代理:RPC 存根把网络调用伪装成本地方法调用
- 审计与计量:统一记录耗时、调用方、入参摘要
- 缓存与限流:按主键缓存结果,或对下游调用做并发限制
- 写时复制:读操作走真实对象,只有修改时才创建副本
保护代理示例
保护代理示例
interface Service { Result call(Request r); }
class AdminProxy implements Service {
private final Service real;
AdminProxy(Service real) { this.real = real; }
@Override
public Result call(Request r) {
User u = CurrentUser.get();
if (!u.isAdmin()) throw new AccessDeniedException();
long t0 = System.nanoTime();
try {
return real.call(r);
} finally {
metrics.record("service.call", System.nanoTime() - t0); // 代理承担横切职责
}
}
}代理类型对照
| 类型 | 拦截点 | 典型实现 |
|---|---|---|
| 虚代理 | 首次访问时创建真实对象 | 懒加载句柄 |
| 保护代理 | 调用前校验权限 | 网关、切面 |
| 远程代理 | 序列化并发送网络请求 | RPC 存根 |
| 日志代理 | 调用前后记录 | 动态代理、中间件 |
| 缓存代理 | 命中则直接返回 | 注解驱动的缓存切面 |
Spring AOP 对接口用 JDK 动态代理、对无接口的类用 CGLIB;Spring Boot 2.0 起默认统一走 CGLIB,可用 spring.aop.proxy-target-class 调整。两种方式都改变不了同一件事:同类内部的自我调用不经过代理。
动态代理会包装检查型异常:JDK 代理对接口未声明的异常会包成 UndeclaredThrowableException。需要在拦截层统一拆包并保留原始异常类型,否则调用方按原类型写的 catch 会失配,错误处理静默走偏。