Skip to content

什么是代理模式?一般用在什么场景? ​

  • 标签:设计模式

🏛 业务架构师 ​

代理模式给目标对象套一层同接口的替身,把"访问控制之外的增强"放进替身里。业务上它最大的落地形态就是 AOP。

我用三个场景讲它解决了什么业务问题:

  • 声明式事务:@Transactional 背后就是代理在方法前后开启/提交/回滚事务。业务代码只写业务规则,事务这种技术关注点由代理统一管理——没有它,每个服务方法首尾各五行事务样板代码,还到处是漏提交的 bug;
  • 审计与合规:金融业务要求所有敏感操作留痕,代理层拦截"修改利率、调整额度"类方法,自动记录操作人、时间、前后值——合规要求变成一段可复用的切面,而不是散落几百处的日志语句;
  • 监控与治理:接口耗时、慢调用、灰度标记都在代理层埋点,业务零感知。

必须讲透的业务坑:Spring 代理的失效场景——内部方法自调用不走代理(事务注解"不生效"的头号原因,需要注入自身或用 AopContext)、final 方法无法被 CGLIB 子类代理。这类坑的共性是"以为增强了,实际没增强",在交易场景就是事务边界错误 = 资损,所以我要求核心事务方法保持"从外部调用"的形态。

一句话:代理模式的价值是让业务代码只表达业务,横切关注点全部收编到代理层统一治理。

🏗 系统架构师 ​

代理模式给目标对象提供一个替身,通过替身访问目标并在前后织入额外逻辑——控制访问,而非增强功能本身。

静态代理 vs 动态代理:

  • 静态代理:手写代理类,编译期确定,类爆炸;
  • JDK 动态代理:运行时基于接口生成 $Proxy 类,Proxy.newProxyInstance + InvocationHandler;Spring AOP 对有接口的 Bean 默认用它;
  • CGLIB:生成目标类的子类覆盖方法,无接口也能代理,Spring 对无接口 Bean 用它。共同限制:JDK 代理不能代理无接口类,CGLIB 不能代理 final 类/方法。

我项目里的落地:

  • 声明式事务 @Transactional:代理拦截方法,包事务开启/提交/回滚;
  • 日志/监控:统一在代理层记录方法耗时、异常上报;
  • RPC:客户端拿到的接口就是代理,屏蔽服务发现、负载均衡、网络序列化;
  • MyBatis Mapper 也是 JDK 代理,接口无实现类,方法调用转成 SQL 执行。

代理是"业务与横切关注点分离"的基石,AOP、RPC、中间件埋点全都构建在它之上。

💻 全栈开发专家 ​

不直接调用目标对象,通过代理对象间接访问,在前后织入额外逻辑。三种形态:

  • 静态代理:手写代理类,代码冗余,基本只在教学里出现。
  • JDK 动态代理:基于接口,运行时用反射生成 Proxy 子类,InvocationHandler.invoke 里加逻辑。
  • CGLIB:基于继承,字节码生成目标类子类,final 类/方法无法代理。
java
// JDK 动态代理最小实现
public Object createProxy(Object target) {
    return Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        (proxy, method, args) -> {
            long start = System.currentTimeMillis();
            try { return method.invoke(target, args); }
            finally { log.info("{} cost {}ms", method.getName(), System.currentTimeMillis() - start); }
        });
}

场景:Spring AOP(事务、缓存、日志、监控全是代理)、MyBatis Mapper 接口(没有实现类,纯 JDK 代理)、Dubbo 消费端(远程调用的本地存根)、HyperLogLog... 凡是"不改业务代码增强能力"都用它。

工程坑(必讲):代理对象代理不了内部自调用——this.methodB() 走的是原对象,事务/缓存注解失效,解法是自注入(@Lazy 自身代理)或拆到另一个 Bean。