大家好,我是程序员强子。
工作中使用 Spring AOP 与事务时,
切面失效、事务不回滚、方法内调用无法触发增强等问题屡见不鲜
今天我们就剖析这些高频问题的根源~
来看下今天的知识点:
-
AOP 核心基础概念以及底层原理:
- 基础概念,5 种通知类型及执行顺序,切点表达式
- 底层流程
- AOP失效场景
-
Spring 事务管理核心:
- 7 种事务传播机制(REQUIRED、REQUIRES_NEW、SUPPORTS、NESTED 等);
- 事务核心组件:PlatformTransactionManager;TransactionDefinition;TransactionStatus;TransactionSynchronizationManager;
- 声明式事务(@Transactional)底层执行流程
AOP 核心
连接点(JoinPoint)
本质是程序执行过程中可被切面拦截的位置
在 Spring AOP 里,因为只支持方法级别的增强,
所以连接点特指 目标对象的每个方法执行时的那个瞬间 / 位置
在代码里,比如UserService有addUser()、getUser()、deleteUser()三个方法
每个方法执行的那一刻(比如调用addUser("张三")时),就是一个独立的连接点
简单说:连接点是 所有能被切面盯上的具体位置,是候选对象
切点(Pointcut)
定义
是匹配连接点的 规则 / **条件 ,是筛选器
作用是从所有连接点里,挑出真正想增强的那些连接点
比如规则execution(* com.example.service.UserService.add*(..))就是切点
UserService 中 有三个候选的连接点: addUser()、getUser()、deleteUser()
最终选中以add开头的方法 addUser()
高频切点表达式
execution:方法精准匹配(最常用)
案例
// 匹配com.xxx.service包下所有类的所有公共方法
@Pointcut("execution(public * com.xxx.service.*.*(..))")
// 匹配com.xxx.service及其子包下所有类的所有方法
@Pointcut("execution(* com.xxx.service..*.*(..))")
- *:匹配任意字符(如返回值、方法名、类名)
- ..:匹配任意层级的包或任意个数 / 类型的参数
@annotation:注解匹配(灵活度高)
demo: 自定义@Log注解
// 1. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {}
// 2. 切点表达式
@Pointcut("@annotation(com.xxx.annotation.Log)")
public void logAnnotationPointcut() {}
@annotation(注解全类名),匹配所有标注了该注解的方法
within:类 / 包匹配(粗粒度)
// 匹配UserService类的所有方法
@Pointcut("within(com.xxx.service.UserService)")
// 匹配com.xxx.service包下所有类的方法
@Pointcut("within(com.xxx.service.*)")
within(包名.类名),匹配指定类或包下所有类的方法
通知(Advice)
是具体动作 ,是增强逻辑
类型
在切点匹配的连接点上执行的增强逻辑, 分 5 种类型:
- 前置(@Before)
- 后置(@After)
- 返回后(@AfterReturning)
- 异常后(@AfterThrowing)
- 环绕(@Around)
正常执行流程
@Around(前) → @Before → 目标方法 → @Around(后) → @AfterReturning → @After
举个具体的例子
目标Service
// 目标Service
@Service
public class UserService {
public String getUserById(Long id) {
System.out.println("目标方法:查询用户信息,id=" + id);
return "用户" + id; // 正常返回,若抛异常可测试@AfterThrowing
}
}
切面类(包含5种通知)
@Aspect
@Component
public class LogAspect {
// 定义切点:匹配UserService的所有方法
@Pointcut("execution(* com.xxx.service.UserService.*(..))")
public void userServicePointcut() {}
@Before("userServicePointcut()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("【@Before】目标方法执行前,方法名:" + joinPoint.getSignature().getName());
}
@After("userServicePointcut()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("【@After】目标方法执行后,方法名:" + joinPoint.getSignature().getName());
}
@AfterReturning(value = "userServicePointcut()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("【@AfterReturning】目标方法正常返回,返回值:" + result);
}
@AfterThrowing(value = "userServicePointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Exception e) {
System.out.println("【@AfterThrowing】目标方法抛出异常,异常信息:" + e.getMessage());
}
@Around("userServicePointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("【@Around】目标方法执行前增强");
// 执行目标方法(必须调用,否则目标方法不执行)
Object result = joinPoint.proceed();
System.out.println("【@Around】目标方法执行后增强");
return result;
}
}
最终执行顺序(正常返回场景)
【@Around】目标方法执行前增强
【@Before】目标方法执行前,方法名:getUserById
目标方法:查询用户信息,id=1
【@Around】目标方法执行后增强
【@After】目标方法执行后,方法名:getUserById
【@AfterReturning】目标方法正常返回,返回值:用户1
注意:@Around是环绕通知, 而@Around(前)和@Around(后)的分界点,就是ProceedingJoinPoint.proceed()方法 ,所以是有 前后之分
举个例子
@Around("userServicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// ----------------- @Around(前)的逻辑 -----------------
System.out.println("[Around前] 目标方法执行前的增强(比如参数校验、日志)");
Object[] args = pjp.getArgs(); // 获取目标方法参数
if (args == null || args.length == 0) {
throw new IllegalArgumentException("参数不能为空"); // 可阻止目标方法执行
}
// ----------------- 执行目标方法 -----------------
Object result = pjp.proceed(); // 调用目标方法(比如UserService.addUser())
// ----------------- @Around(后)的逻辑 -----------------
System.out.println("[Around后] 目标方法执行后的增强(比如处理返回值、记录耗时)");
return result + "_增强后的返回值"; // 可修改目标方法返回值
}
@Around(前):proceed()之前的 参数校验、前置日志
@Around(后):proceed()之后的 修改返回值、后置日志
异常执行流程
@Around(前) → @Before → 目标方法(抛异常) → @AfterThrowing → @After(@Around(后)逻辑不会执行,除非捕获异常)
切面(Aspect)
切点 + 通知的组合
封装横切逻辑的一个单独的类,命名的习惯是:xxxAspect, 比如 LogAspect类
织入(Weaving)
将切面逻辑植入目标对象的过程
Spring AOP 通过运行时动态代理(JDK/CGLIB)实现织入
动态代理 原理介绍 在上一篇文章,欢迎查看
为什么动态代理要生成一个代理对象?
不修改目标对象代码,实现无侵入增强
租房中介就是 代理对象:
- 中介会先帮你核实房源(前置增强);
- 再带你看房(调用目标对象的 租房 方法);
- 最后帮你办合同(后置增强)
代理生成的代理类,会保存到磁盘吗?为啥看不到?
默认情况下只存在于内存中,不会保存到磁盘
生成后直接加载到 JVM 中使用,进程结束后就会被回收
AOP 底层流程
Spring AOP 的底层依赖动态代理和Bean后置处理器实现
核心流程可拆解为:
- 切面扫描解析
- 代理 Bean 创建
- 通知链执行
切面的扫描与解析
Spring AOP 的切面扫描核心类:AnnotationAwareAspectJAutoProxyCreator
是一个 BeanPostProcessor,Bean后置处理器,核心步骤:
-
扫描切面:在 Bean 初始化过程中,扫描容器中所有标注@Aspect注解的类
-
解析切面:
- 将切面中的@Pointcut、@Before等注解解析为 Spring 内部的Advisor对象
- Advisor=切点+通知,AOP 的最小执行单元
-
执行:AnnotationAwareAspectJAutoProxyCreator 通过 findCandidateAdvisors()方法获取所有切面的 Advisor,再通过sortAdvisors()按优先级排序
代理 Bean 的创建
根据切点条件 ,查找合适的目标Bean(之前扫描处理的bean)
接下来不操作 目标Bean,而是通过createProxy()方法创建代理Bean
- 获取当前 Bean 的所有匹配的 Advisor
- 确定代理类型(JDK/CGLIB)
- 通过ProxyFactory创建代理对象并返回
通知的执行链
当调用代理Bean 的目标方法时,不会直接执行目标方法,而是触发通知执行链
核心由ReflectiveMethodInvocation类的proceed()方法实现
执行逻辑拆解:
- 调用proceed()方法时,依次执行所有前置通知;
- 当所有前置通知执行完毕,调用joinPoint.proceed()执行目标方法;
- 目标方法执行后,依次执行后置通知、返回通知 / 异常通知
AOP失效场景
自调用问题
问题现象是怎么样的?
Service 类中方法 A -> 本类方法 B
若方法 B 被 AOP增强,则增强逻辑不执行
底层原因
AOP 的增强逻辑是通过代理 Bean触发的
而内部方法调用时,使用的是this关键字,而非代理对象,因此无法触发通知执行链
解决方案
暴露代理
- 开启暴露代理:在启动类添加@EnableAspectJAutoProxy(exposeProxy = true);
- 通过AopContext.currentProxy()获取代理对象,用代理对象调用方法
@Service
public class UserService {
public void methodA() {
// 用代理对象调用methodB
UserService proxy = (UserService) AopContext.currentProxy();
proxy.methodB();
}
@Log // AOP增强注解
public void methodB() { /* 业务逻辑 */ }
}
依赖注入自身
通过@Autowired将自身注入到当前类
拆分类
将方法 B 抽取到新的Service 类中,通过依赖注入调用,避免内部调用
静态方法 / 私有方法失效
底层原因
-
静态方法无法增强
- JDK代理:基于接口,静态方法属于类,接口中无法定义静态方法,因此无法代理;
- CGLIB代理:基于继承,静态方法是类级别的方法,子类无法重写父类的静态方法,因此无法通过字节码增强植入通知。
-
私有方法无法增强
- CGLIB 代理的子类无法访问父类的私有方法,更无法重写;
- JDK 代理同样无法代理私有方法(接口无私有方法),因此私有方法的 AOP 增强完全不生效
解决方案
- 避免在静态方法 / 私有方法中写需要 AOP增强的逻辑;
- 将静态方法改为实例方法,私有方法改为 public/protected 方法。
Spring 事务
事务传播机制
Spring 定义了 7 种传播机制
| 传播机制 | 事务要求 | 异常影响范围 | 适用场景 |
|---|---|---|---|
| REQUIRED | 有则加入,无则新建 | 整个事务回滚 | 核心业务(下单 + 扣库存) |
| REQUIRES_NEW | 强制新建独立事务 | 仅自身回滚,不影响主事务 | 日志记录、消息发送 |
| SUPPORTS | 可有可无 | 无事务则无影响 | 普通查询 |
| NOT_SUPPORTED | 强制非事务 | 无事务,无回滚 | 纯查询、缓存操作 |
| MANDATORY | 必须有事务 | 无事务抛异常 | 核心数据修改 |
| NEVER | 必须无事务 | 有事务抛异常 | 日志归档、非事务操作 |
| NESTED | 嵌套在主事务中(保存点) | 仅自身回滚,主事务可继续 | 批量操作(部分失败) |
REQUIRED
默认,支持当前事务,无则新建
- 定义:若当前存在事务,则加入当前事务;若当前无事务,则新建事务。
- 特点:所有被REQUIRED修饰的方法共用一个事务,任意方法异常会导致整个事务回滚。
Demo :下单时扣减库存,两者同属一个事务,保证失败同时回滚
// 订单服务(主事务Bean)
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockService stockService; // 库存服务(另一个Bean)
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(String orderNo, String productId, int count) {
// 1. 本Bean内操作(主事务)
orderMapper.insertOrder(orderNo, productId, count);
// 2. 调用另一个Bean的事务方法(加入主事务)
stockService.deductStock(productId, count);
// 模拟异常:整体回滚(订单和库存都回滚)
// int i = 1 / 0;
}
}
// 库存服务(独立Bean)
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(String productId, int count) {
stockMapper.deductStock(productId, count);
}
}
REQUIRES_NEW
新建事务,挂起当前事务
- 定义:无论当前是否有事务,都新建独立事务;若当前有事务,则先挂起当前事务,待新事务完成后恢复。
- 特点:新事务与原事务相互独立,异常仅影响自身,不影响原事务
Demo :下单时记录日志,日志事务独立,即使下单失败,日志仍保存 ,新事物独立,不被原事务影响~
// 订单服务(主事务Bean)
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private LogService logService; // 日志服务(独立Bean)
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(String orderNo) {
orderMapper.insertOrder(orderNo, "P001", 1);
// 调用另一个Bean的REQUIRES_NEW方法(新建独立事务)
logService.recordLog("创建订单:" + orderNo);
// 模拟主事务异常:订单回滚,日志不回滚(新事务已提交)
int i = 1 / 0;
}
}
// 日志服务(独立Bean)
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog(String content) {
logMapper.insertLog(content);
}
}
SUPPORTS
支持当前事务,无则非事务执行
- 定义:若当前存在事务,则加入;若当前无事务,则以非事务方式执行。
- 特点:可有可无 的事务支持,适用于查询类方法
Demo :查询订单详情,若在事务中则加入,否则非事务执行
// 非事务环境调用SUPPORTS方法
@Service
public class NonTxService {
@Autowired
private LogQueryService logQueryService; // 跨Bean调用
// 无事务方法调用SUPPORTS
public void testSupportsWithoutTx() {
logQueryService.log("非事务日志");
// 模拟异常:尝试回滚(但SUPPORTS是非事务执行,无法回滚)
int i = 1 / 0;
}
}
// 事务环境调用SUPPORTS方法
@Service
public class TxService {
@Autowired
private LogQueryService logQueryService; // 跨Bean调用
// 事务方法调用SUPPORTS
@Transactional(propagation = Propagation.REQUIRED)
public void testSupportsWithTx() {
logQueryService.log("事务日志"); // SUPPORTS加入当前事务
// 模拟异常:触发事务回滚
int i = 1 / 0;
}
}
NOT_SUPPORTED
不支持事务,挂起当前事务
- 定义:以非事务方式执行;若当前有事务,则先挂起当前事务,执行完后恢复。
- 特点:强制非事务执行,适用于无需事务的操作(如纯查询、缓存更新)
Demo 场景:查询订单统计,强制非事务执行,即使调用方有事务
@Service
public class LogService { // 独立Bean,提供NOT_SUPPORTED方法
@Autowired
private LogMapper logMapper;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void recordLog(String content) {
logMapper.insertLog(content); // 非事务插入日志
System.out.println("NOT_SUPPORTED:日志已插入");
}
}
@Service
public class OrderService {
@Autowired
private LogMapper logMapper;
@Autowired
private LogService logService; // 跨Bean调用NOT_SUPPORTED方法
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(String orderNo) {
// 1. 外层事务操作:插入订单(受事务控制)
logMapper.insertOrder(orderNo);
System.out.println("外层事务:订单已插入");
// 2. 调用NOT_SUPPORTED方法(强制非事务,挂起当前事务)
logService.recordLog("订单创建日志:" + orderNo);
// 3. 模拟外层事务异常:触发订单回滚,但日志不会回滚
int i = 1 / 0;
}
}
MANDATORY
必须在事务中执行,否则抛异常
- 定义:必须在现有事务中执行;若当前无事务,则直接抛出IllegalTransactionStateException。
- 特点:强制依赖上级事务,适用于 必须在事务中完成 的操作,比如核心数据修改
Demo 场景:订单支付 必须在事务中执行,否则报错
@Service
public class PayService {
@Autowired
private PayMapper payMapper;
@Transactional(propagation = Propagation.MANDATORY)
public void payOrder(String orderNo, BigDecimal amount) {
payMapper.insertPayRecord(orderNo, amount);
}
}
// 测试类
@SpringBootTest
public class TransactionTest {
@Autowired
private PayService payService;
// 非事务调用MANDATORY方法:抛异常
@Test(expected = IllegalTransactionStateException.class)
public void testMandatoryWithoutTx() {
payService.payOrder("O001", new BigDecimal("100"));
}
// 事务中调用:正常执行
@Test
@Transactional
public void testMandatoryWithTx() {
payService.payOrder("O001", new BigDecimal("100"));
}
}
NEVER
必须非事务执行,否则抛异常
- 定义:必须以非事务方式执行;若当前有事务,则抛出IllegalTransactionStateException。
- 特点:与MANDATORY相反,强制禁止事务,适用于绝对不能在事务中执行的操作
@Service
public class ArchiveService {
@Transactional(propagation = Propagation.NEVER)
public void archiveLog() {
// 日志归档逻辑
}
}
// 测试:事务中调用NEVER方法
@SpringBootTest
public class TransactionTest {
@Autowired
private ArchiveService archiveService;
@Test(expected = IllegalTransactionStateException.class)
@Transactional
public void testNeverWithTx() {
archiveService.archiveLog(); // 抛异常
}
}
NESTED
嵌套事务,依赖主事务
-
定义:若当前有事务,则在当前事务内创建嵌套事务(保存点机制);若当前无事务,则新建事务
-
特点
- 嵌套事务是主事务的子事务,主事务回滚则嵌套事务必回滚;
- 嵌套事务回滚不影响主事务(仅回滚到保存点);
Demo 场景:批量下单,其中一个订单失败仅回滚该订单,不影响其他订单
// 批量订单服务(主事务Bean)
@Service
public class BatchOrderService {
@Autowired
private SingleOrderService singleOrderService; // 单个订单服务(独立Bean)
@Transactional(propagation = Propagation.REQUIRED)
public void batchCreateOrder(List<String> orderNos) {
for (String orderNo : orderNos) {
try {
// 调用另一个Bean的NESTED方法(嵌套事务)
singleOrderService.createSingleOrder(orderNo);
} catch (Exception e) {
// 嵌套事务回滚,主事务继续
System.err.println("订单" + orderNo + "失败:" + e.getMessage());
}
}
}
}
// 单个订单服务(独立Bean)
@Service
public class SingleOrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional(propagation = Propagation.NESTED)
public void createSingleOrder(String orderNo) {
orderMapper.insertOrder(orderNo, "P001", 1);
if ("O002".equals(orderNo)) {
throw new RuntimeException("库存不足"); // 嵌套事务回滚
}
}
}
事务核心组件
TransactionDefinition
事务的 规则定义 ,定义事务的属性规则
-
作用
- 传播机制:如REQUIRED、REQUIRES_NEW(决定事务如何传播);
- 隔离级别:如READ_COMMITTED(解决脏读、不可重复读等问题);
- 超时时间:事务最长执行时间(超时自动回滚);
- 只读属性:标记事务是否为只读
-
默认实现
- DefaultTransactionDefinition
- 提供默认值,如传播机制默认 REQUIRED,隔离级别 默认 数据库默认
TransactionAttribute
TransactionAttribute 继承自 TransactionDefinition
并新增了与注解事务相关的扩展方法(主要是异常回滚规则)
专门适配 @Transactional 注解的解析场景
TransactionAttribute 新增的关键方法
// 判断给定异常是否触发事务回滚(适配@Transactional的rollbackFor/noRollbackFor)
boolean rollbackOn(Throwable ex);
// 获取事务的描述(如方法名,用于日志)
String getName();
这些方法是为了支持 @Transactional 注解中的 rollbackFor、noRollbackFor 等属性
TransactionStatus
跟踪事务的实时状态,是事务的 状态记录本
作用是什么?
保存事务执行过程中的状态信息,供PlatformTransactionManager判断如何操作事务(提交 / 回滚)
有哪些核心属性 / 方法?
- isNewTransaction():是否是新建事务(区分 加入现有事务 和 新建事务);
- setRollbackOnly():标记事务必须回滚(即使无异常);
- isRollbackOnly():判断事务是否需要回滚;
- hasSavepoint():是否存在保存点(用于NESTED嵌套事务)
PlatformTransactionManager
事务的 执行者 ,Spring 事务管理的核心接口 ,事务的总指挥
定义了事务的核心操作(获取事务、提交、回滚)
作用是什么?
根据TransactionDefinition的规则,完成事务的生命周期管理(开启、提交、回滚),不同数据源 / 持久化框架有不同实现:
- DataSourceTransactionManager:适配 JDBC/MyBatis(基于java.sql.Connection);
- JpaTransactionManager:适配 JPA(基于 JPA 的EntityManager);
- HibernateTransactionManager:适配 Hibernate(基于Session)
核心方法是什么?
// 根据事务定义,获取/创建事务,返回事务状态
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交事务(根据事务状态判断是否真提交)
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务(根据事务状态判断是否真回滚)
void rollback(TransactionStatus status) throws TransactionException;
TransactionSynchronizationManager
事务的 上下文容器 , 基于ThreadLocal的工具类,是事务的 线程本地仓库
作用是什么?
在当前线程中存储事务相关的上下文信息(如数据库连接、事务状态、同步回调),保证多线程下事务的隔离性
核心存储内容有哪些?
- 事务的Connection(或EntityManager/Session):确保同一线程内的数据库操作使用同一个连接;
- 事务状态(是否激活事务、是否只读);
- 事务同步回调(TransactionSynchronization):事务完成后执行的钩子(如资源清理)
核心方法有哪些?
// 绑定当前线程的数据库连接
static void bindResource(Object key, Object value);
// 获取当前线程绑定的数据库连接
static Object getResource(Object key);
// 解绑当前线程的数据库连接
static void unbindResource(Object key);
// 判断当前线程是否存在活跃事务
static boolean isActualTransactionActive();
TransactionSynchronization
事务同步回调接口,允许在事务的不同生命周期阶段(提交前、提交后、回滚后等)**插入自定义逻辑 **
相当于给事务加了 钩子函数,让我们能在事务关键节点执行额外操作 ,比如提交后发送消息、回滚后清理资源
有哪些核心方法及触发时机?
| 方法 | 触发时机 | 关键注意事项 |
|---|---|---|
| suspend() | 事务被挂起时 比如REQUIRES_NEW 传播机制下,外层事务被挂起 | 暂存事务相关资源 |
| resume() | 事务被恢复时(挂起的事务重新激活) | 恢复暂存的资源 |
| beforeCommit(boolean readOnly) | 事务提交前commit()执行前 | 可做最终数据校验,readOnly 表示是否只读事务 |
| beforeCompletion() | 事务完成前 无论提交 / 回滚 在beforeCommit之后 commit/rollback之前 | 适合做资源清理(如关闭流) |
| afterCommit() | 事务成功提交后 | 仅在事务提交成功后触发,可执行异步操作(如发消息) |
| afterCompletion(int status) | 事务最终完成后(提交 / 回滚都触发) | 区分事务状态STATUS_COMMITTED STATUS_ROLLED_BACK |
使用场景有哪些?
- 事务提交后发送 MQ 消息、更新缓存;
- 事务回滚后清理临时文件 / 数据;
- 事务提交前做数据一致性校验;
- 跨数据源事务的同步(如 MySQL+Redis 的一致性保证)
demo
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private MqProducer mqProducer; // 模拟MQ生产者
@Transactional
public void createOrder(String orderNo) {
// 1. 执行事务操作:插入订单
orderMapper.insertOrder(orderNo);
// 2. 注册事务同步器(仅在事务提交后发送MQ)
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
// 事务提交后执行:发送订单创建成功消息
mqProducer.send("order_topic", "订单" + orderNo + "已创建");
System.out.println("事务提交后发送MQ:" + orderNo);
}
@Override
public void afterCompletion(int status) {
// 事务完成后执行(提交/回滚都触发)
if (status == STATUS_ROLLED_BACK) {
System.out.println("事务回滚:清理订单" + orderNo + "的临时数据");
}
}
});
// 模拟异常:若抛出异常,事务回滚,afterCommit不会执行
// int i = 1 / 0;
}
}
- 无异常:事务提交,afterCommit()触发,MQ 消息发送成功;
- 有异常:事务回滚,afterCommit()不触发,afterCompletion()检测到STATUS_ROLLED_BACK,执行清理逻辑。
简化写法
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
mqProducer.send("order_topic", "订单" + orderNo + "已创建");
}
});
@Transactional底层执行流程
启动时
-
扫描注解:
- AnnotationTransactionAttributeSource会扫描 Bean 中标记@Transactional的方法 / 类,
- 解析注解属性(传播机制、隔离级别、超时时间等),封装为TransactionAttribute (继承自 TransactionDefinition) 。
-
创建事务切面:Spring 将TransactionInterceptor(通知)与TransactionAttributeSource(切点规则)组合成BeanFactoryTransactionAttributeSourceAdvisor(事务切面),注册到容器中。
Bean 初始化
当容器初始化带有@Transactional的 Bean
- AnnotationAwareAspectJAutoProxyCreator(AOP 自动代理创建器)检测到该 Bean 匹配事务切面的切点;
- 根据 Bean 类型选择代理方式(JDK 动态代理 / CGLIB),生成事务代理对象;
- 代理对象的方法调用会被TransactionInterceptor拦截。
方法调用时
调用代理对象的事务方法时,TransactionInterceptor的invoke()方法会执行以下步骤:
-
获取事务属性:从TransactionAttributeSource中获取当前方法的@Transactional配置(如传播机制、隔离级别)
-
获取事务管理器:根据数据源类型匹配对应的PlatformTransactionManager(如DataSourceTransactionManager)
-
开启 / 加入事务
- 调用PlatformTransactionManager.getTransaction(...),根据传播机制决定
- 即根据事务传播机制处理
-
执行目标方法:调用目标对象的真实方法
-
事务提交 / 回滚
- 若方法正常返回:调用PlatformTransactionManager.commit()提交事务
- 若方法抛出异常:根据rollbackFor判断是否回滚,是则调用rollback(),否则提交
核心细节
- 事务与线程绑定:TransactionSynchronizationManager通过 ThreadLocal 存储当前线程的事务信息(如Connection、事务状态),确保多线程下事务隔离。
- 异常回滚规则:默认仅回滚RuntimeException和Error,需通过rollbackFor指定检查型异常(如@Transactional(rollbackFor = Exception.class))。
- 代理对象的必要性:若直接调用目标对象的方法(非代理),会绕过TransactionInterceptor,事务失效(即 自调用问题)。
总结
今天把 Spring AOP 与事务的核心底层逻辑给学扎实了!
本文聚焦 Spring AOP 与事务四大核心实战内容:
- AOP 五大核心基础概念,@Before 等 5 种通知类型的执行顺序,以及 execution、@annotation、within 三种切点表达式的核心用法;
- Spring AOP 的完整底层流程(切面扫描解析、代理 Bean 创建、通知执行链调用),以及 AOP 失效的典型场景;
- 事务 7 种传播机制(含 REQUIRED、REQUIRES_NEW、SUPPORTS、NESTED 等核心场景),及 PlatformTransactionManager、TransactionDefinition 等四大事务核心组件的作用;
- 声明式事务 @Transactional 注解的底层执行流程,从注解解析到事务开启、提交 / 回滚的全链路逻辑。
帮 我们 吃透 Spring AOP 与事务底层,从会用到懂原理~~
熟练度刷不停,知识点吃透稳,下期接着练~