一、 核心思想:代理模式 + aop
想象一下,你是一位非常重要的业务专家(你的 service 层代码),你的工作是处理核心业务逻辑(比如转账、更新用户信息)。但每次处理业务前,你都需要做一些准备工作(开启事务),结束后还要做一些收尾工作(提交或回滚事务)。这些准备和收尾工作繁琐、重复,而且和你的核心业务关系不大。
声明式事务管理的目标就是:让你这位专家只专注于核心业务,把所有繁琐的事务管理工作交给一个“管家”来处理。
这个“管家”就是 spring aop 创建的代理对象 (proxy)。
aop (aspect-oriented programming, 面向切面编程) 在这里扮演的角色就是:
- 定义“切面” (aspect):将事务管理(开启、提交、回滚)这个“横切关注点”从业务代码中抽离出来,形成一个独立的模块。
- 定义“切点” (pointcut):精确地定义这个“管家”应该在哪些方法执行前后介入。在 spring 中,这通常是通过 @transactional 注解来指定的。
- 创建“代理” (proxy):spring 不会直接返回你的原始业务对象,而是会创建一个该对象的代理。所有对业务对象的调用都会先经过这个代理。
二、 详细工作流程(一步步拆解)
让我们跟踪一个对 @transactional 方法的调用,看看 spring 内部到底发生了什么。
场景:一个 usercontroller 调用一个 userservice 的 updateuser() 方法,该方法被 @transactional 注解。
// 业务接口 public interface userservice { void updateuser(user user); } // 业务实现类 @service public class userserviceimpl implements userservice { @autowired private userrepository userrepository; @override @transactional // <--- 关键注解 public void updateuser(user user) { // 核心业务逻辑 userrepository.update(user); // 假设这里如果出错了,需要回滚 if (user.getname() == null) { throw new illegalargumentexception("username cannot be null"); } } } // 调用方 @restcontroller public class usercontroller { @autowired private userservice userservice; // <--- 注意:这里注入的其实是代理对象 @postmapping("/user/update") public void updateuser(@requestbody user user) { userservice.updateuser(user); } }
阶段一:应用启动时 - 创建代理
1.bean 扫描:spring 容器在启动时,会扫描所有的 bean。
2.识别 @transactional:当 spring 扫描到 userserviceimpl 时,它会发现这个类或其方法上存在 @transactional 注解。
3.创建代理对象:spring 认识到这个 bean 需要被事务“增强”(advised)。它不会直接创建 userserviceimpl 的实例并放入容器,而是会通过 aop 框架为它创建一个代理对象。
- 如果 userserviceimpl 实现了接口(如本例中的 userservice),spring 默认会使用 jdk 动态代理来创建一个实现了 userservice 接口的代理类。
- 如果 userserviceimpl 没有实现任何接口,spring 会使用 cglib 来创建一个 userserviceimpl 的子类作为代理。
4.注入代理:当 usercontroller 通过 @autowired 请求注入 userservice 时,spring 容器会将上一步创建的那个代理对象注入给它,而不是原始的 userserviceimpl 对象。usercontroller 对此毫不知情,它以为自己拿到的就是普通的 userservice。
阶段二:方法调用时 - 事务拦截
1.调用入口:usercontroller 调用 userservice.updateuser(user)。
2.命中代理:这个调用首先命中的是代理对象的 updateuser 方法,而不是原始对象的。
3.事务拦截器 (transactioninterceptor) 生效:代理对象的这个方法中包含了 aop 的“通知”(advice)。对于事务来说,这个通知的具体实现就是 transactioninterceptor。这个拦截器就像一个警卫,拦住了这次调用。
4.开启事务:
- transactioninterceptor 首先会解析 updateuser 方法上 @transactional 注解的属性(如传播行为 propagation、隔离级别 isolation、是否只读 readonly 等)。
- 它向事务管理器 (platformtransactionmanager) 请求一个事务。
- 事务管理器(例如 datasourcetransactionmanager)会从数据库连接池中获取一个连接 (connection),然后执行 connection.setautocommit(false),这标志着事务的正式开始。
5.调用原始方法:事务成功开启后,transactioninterceptor 会通过反射调用原始 userserviceimpl 对象的 updateuser 方法。
6.执行业务逻辑:此时,才真正开始执行你编写的核心业务代码,比如 userrepository.update(user)。所有这些数据库操作都将在上一步获取的那个被 spring 管理的 connection 上执行。
阶段三:方法执行后 - 提交或回滚
当原始的 updateuser 方法执行完毕后,控制权返回到 transactioninterceptor。
1.正常执行完毕 (没有抛出异常):
- 拦截器捕获到方法正常返回。
- 它会通知事务管理器提交 (commit) 事务。
- 事务管理器调用 connection.commit(),将本次事务中的所有数据库操作永久保存。
2.抛出异常:
- updateuser 方法内部抛出了一个异常(默认是 runtimeexception 或 error)。
- 拦截器在其 try-catch 块中捕获到这个异常。
- 它会通知事务管理器回滚 (rollback) 事务。
- 事务管理器调用 connection.rollback(),撤销本次事务中的所有数据库操作。
- 最后,拦截器会将捕获到的异常继续向上抛出,以便上层调用者(如 usercontroller 或全局异常处理器)能够感知到。
阶段四:收尾
无论事务是提交还是回滚,事务管理器最终都会将数据库连接释放回连接池。
三、 核心组件总结
@transactional:元数据,告诉 aop 在哪里以及如何应用事务。它本身不包含任何逻辑。
aop 代理 (proxy):由 spring 动态创建的“管家”对象,是事务逻辑的实际入口。它包装了原始的业务对象。
事务拦截器 (transactioninterceptor):aop 通知(advice)的具体实现,是代理对象中的核心逻辑。它负责在目标方法调用前后开启、提交或回滚事务。
事务管理器 (platformtransactionmanager):一个统一的事务管理接口。spring 通过它来适配不同的数据源技术(如 jdbc, jpa/hibernate, jta)。它负责执行真正的事务操作(begin, commit, rollback)。
四、 为什么 @transactional 在某些情况下会失效?
理解了以上原理,就很容易明白为什么 @transactional 会在以下情况失效:
1.方法不是 public 的:cglib 代理是通过继承来实现的,而 private 或 protected 方法无法被子类重写(override),所以 aop 无法拦截。jdk 动态代理基于接口,更不存在非 public 方法。
2.同一个类中的方法调用(this 调用):
@service public class userserviceimpl implements userservice { public void entrymethod() { this.updateuser(user); // <--- 失效! } @transactional public void updateuser(user user) { // ... } }
当外部调用 entrymethod() 时,进入的是 userserviceimpl 的原始对象(如果 entrymethod 没有被代理)。然后 this.updateuser() 是一个对象内部的直接调用,它绕过了代理对象,直接调用了原始对象的 updateuser 方法,因此事务拦截器根本没有机会介入。
3.异常被 catch 掉了:
@transactional public void updateuser(user user) { try { // ... 抛出 runtimeexception 的代码 } catch (exception e) { // 异常被吞了,没有继续抛出 } }
事务拦截器是通过捕获异常来决定是否回滚的。如果异常被你的 try-catch 块“消化”了,拦截器就认为方法是正常执行完毕的,于是它会提交事务,而不是回滚。
通过这个机制,spring 完美地将业务逻辑和非功能性的事务管理代码解耦,让开发者能更专注于业务价值的实现,代码也变得更加清晰和易于维护。
到此这篇关于spring如何使用aop实现声明式事务管理的文章就介绍到这了,更多相关spring aop声明式事务管理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论