首页/开发/代码质量与评审速查/代理模式 Proxy(结构型)
设计原则与模式62 条

代理模式 Proxy(结构型)

为其他对象提供同一接口的替身,控制访问:用于延迟加载、访问控制、日志审计、远程调用(RPC 存根);与装饰器的差别在意图——代理控制,装饰增强。

代理模式是什么

为其它对象提供同一接口的替身,在调用真正实现前后插入控制逻辑:鉴权、计数、缓存、延迟初始化、远程调用。代理与真实对象实现同一接口,调用方通常无感,只多出一层间接调用。与装饰器的差别在意图——代理控制访问,装饰增强行为;两者实现上可以长得一样,靠动机区分。

基本信息

类型虚代理(延迟加载)/ 保护代理(鉴权)/ 远程代理 / 日志审计
对比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 会失配,错误处理静默走偏。

易错点

  • 类内自调用绕过代理:@Transactional 方法调用同类的事务方法,事务不生效
  • 动态代理吞掉原始异常类型:调用方按原类型 catch 失效,错误处理逻辑永不触发
  • 代理层堆积业务逻辑:职责从真实对象迁移过来,两处都要维护且难判断谁是权威
  • 代理链过长:日志、缓存、事务各一层,出问题时栈信息难读,开销也从量变走到质变

数据来源与参考

  • 《设计模式》(GoF)· Proxy
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌