什么是代理模式?一般用在什么场景?
- 标签:设计模式
🏛 业务架构师
代理模式给目标对象套一层同接口的替身,把"访问控制之外的增强"放进替身里。业务上它最大的落地形态就是 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。