为什么 Spring 能自动回滚事务?揭秘代理模式
·

在娱乐圈,你想请刘德华唱歌,你不能直接打他电话。
你必须先打给他的经纪人 (Agent)。
经纪人负责谈价钱、签合同、安排场地。
只有当这些杂事都搞定后,经纪人才会把话筒递给刘德华:“华仔,该你上台唱了。”
在 Java 世界里,代理模式 就是这个“经纪人”。
它在 不修改原对象代码 的前提下,控制了对原对象的访问,并能**“夹带私货”**(比如加日志、加权限、加事务)。
这就是 Spring AOP 的核心灵魂。
🎭 一、技术分析:静态 vs 动态
代理模式在 Java 中经历了从“石器时代”到“工业时代”的进化。
1. 静态代理 (Static Proxy) —— “专属经纪人”
- 原理: 你手动写一个
Agent类,里面持有一个Star对象。 - 代码:
class Agent implements Star {
private Star realStar;
public void sing() {
System.out.println("经纪人:谈合同..."); // 前置增强
realStar.sing(); // 真实业务
System.out.println("经纪人:收钱..."); // 后置增强
}
}
- 缺点: 累死人。刘德华有一个经纪人,张学友也得配一个。如果你有 100 个 Service 类,你就得写 100 个代理类。
2. 动态代理 (Dynamic Proxy) —— “万能经纪公司”
- 原理: 不需要手写代理类。Java 在运行期间,利用 反射机制 自动生成一个代理对象。
- 核心:
Proxy.newProxyInstance()和InvocationHandler。 - 优点: 一个 Handler 就能代理无数个类。不管你是刘德华还是张学友,只要想唱歌,我这个通用逻辑都能给你加上“谈合同”和“收钱”的步骤。
🛡️ 二、面试重灾区:JDK 动态代理 vs CGLIB
这是面试官最爱问的“大家来找茬”。Spring 会自动在两者之间切换。
1. JDK 动态代理 (官方正统)
- 规则: 必须要实现接口 (Interface)。
- 原理: 它是利用反射,生成一个实现了同样接口的匿名类。
- 局限: 如果刘德华没有“歌手证”(没实现接口),JDK 就代理不了他。
2. CGLIB 动态代理 (第三方黑科技)
-
规则: 不需要接口,直接代理类。
-
原理: 继承 (Inheritance)。它在内存中动态生成一个
LiuDeHua的子类,重写父类的方法。 -
这也是为什么 Spring 中被代理的方法不能是
final的(因为 final 不能被继承)。 -
场景: 如果你的 Service 没有接口,Spring Boot 2.x 默认会用 CGLIB。
🎬 三、故事场景:Spring AOP 的魔法
为了搞懂 AOP (面向切面编程),我们将 代理模式 比作 舞台演出。
1. 原始代码 (没有 AOP)
public void saveUser() {
// 1. 开启事务 (杂事)
transaction.begin();
// 2. 真正的业务 (唱歌)
userDao.save();
// 3. 提交事务 (杂事)
transaction.commit();
}
- 痛点: 业务代码(唱歌)和非业务代码(杂事)混在一起。如果你有 100 个方法,这几行事务代码要复制 100 遍。
2. 代理模式 (AOP)
Spring 创建了一个 代理对象 (Proxy)。
- 你的代码:
@Transactional
public void saveUser() {
userDao.save(); // 只管唱歌
}
- Spring 幕后生成的代理:
public void saveUserProxy() {
try {
transactionManager.begin(); // 前置通知 (Before Advice)
target.saveUser(); // 回调你的目标方法
transactionManager.commit();// 返回通知 (AfterReturning)
} catch (Exception e) {
transactionManager.rollback(); // 异常通知 (AfterThrowing)
}
}
结论: 你以为你注入的是 UserService,其实 Spring 偷偷把它掉包成了 UserService$$EnhancerBySpringCGLIB。
🚀 四、实战:什么时候用代理?
- 日志记录: 不要在每个方法里写
log.info("开始执行...")。写个 AOP,自动在所有 Controller 方法执行前打印日志。 - 权限控制: 不要在每个方法里检查
if (user.isAdmin())。写个代理,没权限的直接拦截。 - 延迟加载 (Lazy Loading):
- MyBatis 查询 User 时,User 里面有个
List<Order>。 - 此时 MyBatis 返回的 List 其实是一个代理对象。
- 只有当你真正调用
list.get(0)时,代理对象才会去数据库查询订单。这叫“虚代理”。
🎯 五、总结:无侵入式的增强
代理模式的灵魂在于 “无侵入”。
它像一个透明的保护壳,包裹在真实对象外面。
外界看起来还是在调用原对象,但实际上,所有的请求都先经过了代理的过滤和加工。
更多推荐




所有评论(0)