声明式事务是 Spring 最受欢迎的特性之一,开发者只需在方法或类上标注一个 @Transactional,方法的执行就会被事务包裹起来,异常时自动回滚,正常时自动提交。这层看似神奇的能力,本质上是一套基于 AOP 动态代理的拦截机制。理解它的实现原理,不仅能回答面试中的高频问题,更能解释日常开发中事务失效、传播行为异常等现象的根本原因。本文将从注解解析、代理创建、拦截器执行、事务管理器协作四个层面,完整拆解这套机制。

一、事务切面是如何被注册到容器中的
Spring Boot 中事务功能通过自动配置类 TransactionAutoConfiguration 开启。当容器检测到存在 PlatformTransactionManager 的 Bean 时,会导入 EnableTransactionManagementConfiguration,它等价于在配置类上标注了 @EnableTransactionManagement。这个注解通过 @Import 引入了 TransactionManagementConfigurationSelector,最终向容器注册两个关键 Bean。
第一个是 TransactionInterceptor,也就是事务拦截器,它继承自 MethodInterceptor,是真正干活的通知逻辑。第二个是 BeanFactoryTransactionAttributeSourceAdvisor,这是事务切面,它内部持有拦截器和一个 TransactionAttributeSource,用于判断哪些方法需要被事务增强。两者的装配代码大致如下:
@Configuration
public class ProxyTransactionManagementConfiguration {
@Bean
public TransactionAttributeSource transactionAttributeSource() {
// 负责解析 @Transactional 注解,封装事务属性
return new AnnotationTransactionAttributeSource();
}
@Bean
public TransactionInterceptor transactionInterceptor(
TransactionAttributeSource tas) {
return new TransactionInterceptor(null, tas);
}
@Bean(name = Advisor.TRANSACTION_ADVISOR_BEAN_NAME)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource tas, TransactionInterceptor ti) {
// 切面 = 切点(由 TransactionAttributeSource 提供) + 通知(拦截器)
BeanFactoryTransactionAttributeSourceAdvisor advisor =
new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(tas);
advisor.setAdvice(ti);
return advisor;
}
}注意这里没有一个显式的 Pointcut 表达式。事务的切点判断逻辑隐藏在 TransactionAttributeSource 中:它通过 TransactionAttributeSourcePointcut 的 matches 方法,检查目标方法或目标类上能否解析出 @Transactional 注解。解析过程由 AnnotationTransactionAttributeSource 委托给 SpringTransactionAnnotationParser 完成,注解中的传播行为、隔离级别、超时时间、回滚规则等属性会被封装成 RuleBasedTransactionAttribute 对象并缓存起来,避免重复反射解析。
切面 Bean 注册之后,AbstractAdvisorAutoProxyCreator 会在每个 Bean 初始化后遍历所有 Advisor,只要切点匹配成功,就会为这个 Bean 创建代理对象。这就是为什么加了注解的 Service 类,注入进来的是代理而不是原始对象。
二、TransactionInterceptor 环绕通知的完整执行流程
当外部调用代理对象的方法时,调用链最终进入 TransactionInterceptor 的 invoke 方法。它是标准的环绕通知,核心逻辑浓缩后如下:
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = AopUtils.getTargetClass(invocation.getThis());
TransactionAttributeSource tas = getTransactionAttributeSource();
// 1. 解析当前方法的事务属性(含注解元数据)
TransactionAttribute txAttr = tas.getTransactionAttribute(
invocation.getMethod(), targetClass);
// 2. 获取事务管理器,例如 DataSourceTransactionManager
PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 3. 创建事务,得到 TransactionInfo(状态 + 旧事务信息)
TransactionInfo txInfo = createTransactionIfNecessary(
tm, txAttr, invocation.getMethod().getSimpleName());
Object retVal;
try {
// 4. 执行原始业务方法,即真正的事务边界内的代码
retVal = invocation.proceed();
} catch (Throwable ex) {
// 5. 异常时判断是否回滚,并抛出原异常
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupAfterCompletion(txInfo);
}
// 6. 正常返回时提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}第 3 步是最关键的部分。createTransactionIfNecessary 会调用 PlatformTransactionManager 的 getTransaction 方法,以最常用的 DataSourceTransactionManager 为例,它会执行三件事:开启数据库连接并调用 setAutoCommit(false),将连接通过 TransactionSynchronizationManager 绑定到当前线程的 ThreadLocal(key 是 DataSource),再把 ConnectionHolder 放入新建的 TransactionStatus。此后,MyBatis 或 JdbcTemplate 获取连接时,都是从这个 ThreadLocal 里拿同一个连接,从而保证多个 SQL 落在同一事务内。
第 5 步的回滚判断由 RuleBasedTransactionAttribute.rollbackOn 完成。默认规则是:异常类型是 Error 或 RuntimeException 时回滚,受检异常(如 Exception 的直接子类)则提交。这就是为什么手动抛出受检异常事务不会回滚,除非在注解上显式声明 rollbackFor = Exception.class。执行顺序上,Spring 会先匹配注解中配置的 rollbackFor 和 noRollbackFor 规则列表,命中即返回结论,都没命中才走默认规则。
三、常见事务失效场景的原理分析
理解了拦截机制,很多失效场景就能从根源上解释。最典型的是自调用失效:同一个类中方法 A 调用带注解的方法 B,事务不生效。因为 A 中通过 this.b() 调用的是原始对象的方法,绕过了代理,拦截器根本没有机会介入。解决办法包括注入自身代理对象(通过 AopContext.currentProxy() 开启 exposeProxy)或把方法拆到另一个 Bean 中。
其次是传播行为的实现。PROPAGATION_REQUIRES_NEW 会把当前线程绑定的旧连接挂起(保存到挂起资源栈),开启新连接执行内层事务,结束后再恢复旧连接;PROPAGATION_NESTED 则是通过 Savepoint 在同一连接上打保存点,回滚时只回滚到保存点。这两种行为依赖底层驱动的支持,例如 MySQL 需要支持保存点的数据源配置,否则会抛出 NestedTransactionNotSupportedException。
另外要注意异常被 try-catch 吞掉的情况。拦截器只感知穿过 invocation.proceed() 的异常,如果业务代码在方法内部把异常捕获且没有重新抛出,拦截器会认为方法正常结束并执行提交,回滚自然不会发生。同理,方法必须是 public 的,因为默认的代理策略(JDK 动态代理或 CGLIB)对非 public 方法无法保证拦截生效,Spring 会直接跳过非 public 方法上的注解解析。
四、总结:一条注解背后的完整链路
把整条链路串起来看:容器启动时,@EnableTransactionManagement 注册了事务切面和拦截器;Bean 初始化阶段,AnnotationTransactionAttributeSource 解析注解元数据,匹配成功的 Bean 被创建为代理;运行期调用到达代理,进入 TransactionInterceptor 环绕通知,由事务管理器开启连接、绑定 ThreadLocal、执行业务方法,最后依据异常类型和回滚规则决定提交或回滚。
掌握这条链路的价值在于遇到问题时有明确的排查方向:先确认调用的是代理对象而非 this,再确认异常类型是否命中回滚规则,最后检查事务管理器与数据源的绑定关系。声明式事务并不神秘,它只是把样板代码从业务方法中搬到了拦截器里,理解了 AOP 的通知模型,事务、缓存、异步等所有基于代理的 Spring 特性也就一通百通了。
@TransactionalSpring事务AOP通知修改时间:2026-09-04 21:14:40