代理模式
1. 大白话:这是什么?解决什么痛点?
代理模式的核心就是:不直接访问真实对象,而是在它前面放一个“替身”代理对象,由代理对象接收调用、控制访问,并在真正执行前后统一加增强逻辑。
它解决的痛点是“业务代码不想被公共逻辑污染”。比如日志、鉴权、事务、缓存、限流、远程调用、工具调用安全检查,这些逻辑不属于核心业务,但又经常要包在业务方法前后。如果每个方法里都手写一遍,代码会重复、耦合高、牵一发而动全身。
代理模式的价值就是把“核心业务”和“横切增强”拆开:真实对象只做业务,代理对象负责拦截、增强、控制访问。面试里要先把边界说清楚:它不是单纯封装对象,而是客户端调用代理,代理再决定如何访问真实对象。动态代理则进一步解决了静态代理维护成本高的问题,让代理类在运行期自动生成,避免给每个目标类手写一套代理。
2. 底层机制与高频考点
代理模式要按“静态代理 -> JDK 动态代理 -> CGLIB 动态代理 -> Spring AOP”这条线讲。
静态代理
- 手写一个代理类,和目标类实现同一个接口。
- 代理类里持有目标对象,方法调用前后加增强逻辑。
- 从 JVM 角度看,接口、目标类、代理类在编译后都会变成固定的
.class文件,代理关系在编译期就写死了。 - 优点是简单直观;缺点是每个目标类都要写代理类,接口新增方法时目标类和代理类都要改,维护成本高。
JDK 动态代理
- 运行时通过
Proxy.newProxyInstance生成代理类。 - 三个核心参数是类加载器、目标对象实现的接口数组、
InvocationHandler。 - 代理对象调用方法时,会统一转发到
InvocationHandler#invoke,可以在invoke里做日志、事务、权限等增强。 - 目标类必须实现接口,因为 JDK 动态代理本质是在内存里动态生成一个实现同款接口的代理类,它不是目标类的子类。
- 运行时通过
CGLIB 动态代理
- 运行时通过 CGLIB/ASM 字节码技术生成目标类的子类。
- 方法调用会进入
MethodInterceptor#intercept,和 JDK 动态代理里的invoke类似。 - 不要求目标类实现接口。
- 限制是不能代理
final类,也不能增强final、private方法,因为它依赖继承和方法重写。
Spring AOP 的关键考点是:Spring 容器里注入进来的通常是代理对象,不是裸的目标对象。如果目标类实现了接口,Spring 默认倾向使用 JDK 动态代理;如果没有实现接口,就会使用 CGLIB 生成子类代理。外部调用 Bean 方法时,会先经过代理对象,所以 @Transactional、日志切面、权限切面才能生效。
但如果在同一个类内部用 this.xxx() 调用另一个方法,这次调用不会经过 Spring 代理对象,而是直接打到原始对象,所以会出现 AOP 自调用失效。典型表现就是同类内部调用 @Transactional 方法,事务没有开启。
面试对比可以这样记:
| 方案 | 代理关系时机 | 依赖条件 | 底层方式 | 典型限制 |
|---|---|---|---|---|
| 静态代理 | 编译期写死 | 通常依赖共同接口 | 手写代理类持有目标对象 | 类太多,接口变更维护重 |
| JDK 动态代理 | 运行期生成 | 目标类必须实现接口 | 生成接口实现类,调用进 InvocationHandler#invoke | 没接口就代理不了 |
| CGLIB | 运行期生成 | 目标类可被继承 | 生成目标类子类,调用进 MethodInterceptor#intercept | final 类、final/private 方法无法增强 |
3. 🎯 实战口径
在我的项目里,代理模式最典型的落点是 Spring AOP、事务控制和 Advisor 链拦截。
比如 [[黑马点评]] 的秒杀异步下单里,我遇到过 @Transactional 自调用失效问题。订单创建方法需要开启事务,但如果在同一个类里直接用 this.createVoucherOrder() 调用,实际绕过了 Spring 生成的代理对象,事务拦截器不会执行。所以我在主线程中通过 AopContext.currentProxy() 拿到当前 Service 的代理对象,并保存给异步消费线程使用,再通过 proxy.createVoucherOrder() 调用事务方法,保证调用链能进入 Spring AOP 的事务增强逻辑。
这里还有一个工程边界:我把 Redisson 分布式锁包在代理调用外层,而不是写在事务方法内部。这样可以保证事务方法执行完成后再释放锁,避免“锁先释放、事务还没提交”的并发可见性问题。本质上,我不是为了形式上拿代理,而是为了让事务、锁和业务执行顺序都落在正确的调用链上。
在 [[苍穹外卖AI客服]] 里,我也可以用代理思想解释 Advisor 链。比如 SafeToolCallAdvisor 会在大模型继续调用工具前先做一层拦截:统计工具调用轮次、检测重复工具签名,如果发现 LLM 可能陷入工具调用死循环,就短路返回兜底响应。它和传统 Spring AOP 不完全等价,但思想类似:真实能力是“调用模型或工具”,Advisor 像代理层一样在前后加控制逻辑,把安全边界、限流、缓存、记忆注入这些横切能力从核心业务里拆出来。
如果面试官追问代理模式和装饰器模式的区别,我会这样答:两者都能增强对象,但代理模式更强调控制访问,比如事务、权限、远程代理、懒加载、安全拦截;装饰器模式更强调功能叠加,比如 IO 流一层层包能力。Spring AOP 这类场景更符合代理模式,因为它关注的是方法调用能不能被拦截、事务能不能被织入、访问链路是否经过代理对象。
相关链接:[[苍穹外卖AI客服]] | [[黑马点评]]