ava 王者修炼手册【Spring 篇 - AOP 与事务】:底层原理 + 实战避坑全攻略

大家好,我是程序员强子。
工作中使用 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:方法精准匹配(最常用)
案例
*:匹配任意字符(如返回值、方法名、类名)
..:匹配任意层级的包或任意个数 / 类型的参数
@annotation:注解匹配(灵活度高)
demo: 自定义 @Log 注解
@annotation(注解全类名),匹配所有标注了该注解的方法
within:类 / 包匹配(粗粒度)
within(包名.类名),匹配指定类或包下所有类的方法
通知(Advice)
是具体动作 ,是增强逻辑
类型
在切点匹配的连接点上执行的增强逻辑,分 5 种类型:
前置(@Before)
后置(@After)
返回后(@AfterReturning)
异常后(@AfterThrowing)
环绕(@Around)
正常执行流程
@Around(前) → @Before → 目标方法 → @Around(后) → @AfterReturning → @After
举个具体的例子
目标 Service
切面类(包含 5 种通知)
最终执行顺序(正常返回场景)
注意:**@Around 是环绕通知, 而 @Around(前)和 @Around(后)的分界点,就是 ProceedingJoinPoint**.proceed()方法 ,所以是有 前后之分
举个例子
@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()获取代理对象,用代理对象调用方法
依赖注入自身
通过 @Autowired 将自身注入到当前类
拆分类
将方法 B 抽取到新的 Service 类中,通过依赖注入调用,避免内部调用
静态方法 / 私有方法失效
底层原因
静态方法无法增强 JDK 代理:基于接口,静态方法属于类,接口中无法定义静态方法,因此无法代理;CGLIB 代理:基于继承,静态方法是类级别的方法,子类无法重写父类的静态方法,因此无法通过字节码增强植入通知。
私有方法无法增强 CGLIB 代理的子类无法访问父类的私有方法,更无法重写;JDK 代理同样无法代理私有方法(接口无私有方法),因此私有方法的 AOP 增强完全不生效
解决方案
避免在静态方法 / 私有方法中写需要 AOP 增强的逻辑;
将静态方法改为实例方法,私有方法改为 public/protected 方法。
Spring 事务
事务传播机制
Spring 定义了 7 种传播机制
REQUIRED
默认,支持当前事务,无则新建
定义:若当前存在事务,则加入当前事务;若当前无事务,则新建事务。
特点:所有被 REQUIRED 修饰的方法共用一个事务,任意方法异常会导致整个事务回滚。
Demo :下单时扣减库存,两者同属一个事务,保证失败同时回滚
REQUIRES_NEW
新建事务,挂起当前事务
定义:无论当前是否有事务,都新建独立事务;若当前有事务,则先挂起当前事务,待新事务完成后恢复。
特点:新事务与原事务相互独立,异常仅影响自身,不影响原事务
Demo :下单时记录日志,日志事务独立,即使下单失败,日志仍保存 ,新事物独立,不被原事务影响~
SUPPORTS
支持当前事务,无则非事务执行
定义:若当前存在事务,则加入;若当前无事务,则以非事务方式执行。
特点:可有可无 的事务支持,适用于查询类方法
Demo :查询订单详情,若在事务中则加入,否则非事务执行
NOT_SUPPORTED
不支持事务,挂起当前事务
定义:以非事务方式执行;若当前有事务,则先挂起当前事务,执行完后恢复。
特点:强制非事务执行,适用于无需事务的操作(如纯查询、缓存更新)
Demo 场景:查询订单统计,强制非事务执行,即使调用方有事务
MANDATORY
必须在事务中执行,否则抛异常
定义:必须在现有事务中执行;若当前无事务,则直接抛出 IllegalTransactionStateException。
特点:强制依赖上级事务,适用于 必须在事务中完成 的操作,比如核心数据修改
Demo 场景:订单支付 必须在事务中执行,否则报错
NEVER
必须非事务执行,否则抛异常
定义:必须以非事务方式执行;若当前有事务,则抛出 IllegalTransactionStateException。
特点:与 MANDATORY 相反,强制禁止事务,适用于绝对不能在事务中执行的操作
NESTED
嵌套事务,依赖主事务
定义:若当前有事务,则在当前事务内创建嵌套事务(保存点机制);若当前无事务,则新建事务
特点嵌套事务是主事务的子事务,主事务回滚则嵌套事务必回滚;嵌套事务回滚不影响主事务(仅回滚到保存点);
Demo 场景:批量下单,其中一个订单失败仅回滚该订单,不影响其他订单
事务核心组件
TransactionDefinition
事务的 规则定义 ,定义事务的属性规则
作用传播机制:如 REQUIRED、REQUIRES_NEW(决定事务如何传播);隔离级别:如 READ_COMMITTED(解决脏读、不可重复读等问题);超时时间:事务最长执行时间(超时自动回滚);只读属性:标记事务是否为只读
默认实现 DefaultTransactionDefinition 提供默认值,如传播机制默认 REQUIRED,隔离级别 默认 数据库默认
TransactionAttribute
TransactionAttribute 继承自 TransactionDefinition
并新增了与注解事务相关的扩展方法(主要是异常回滚规则)
专门适配 @Transactional 注解的解析场景
TransactionAttribute 新增的关键方法
这些方法是为了支持 @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)
核心方法是什么?
TransactionSynchronizationManager
事务的 上下文容器 , 基于 ThreadLocal 的工具类,是事务的 线程本地仓库
作用是什么?
在当前线程中存储事务相关的上下文信息(如数据库连接、事务状态、同步回调),保证多线程下事务的隔离性
核心存储内容有哪些?
事务的 Connection(或 EntityManager/Session):确保同一线程内的数据库操作使用同一个连接;
事务状态(是否激活事务、是否只读);
事务同步回调(TransactionSynchronization):事务完成后执行的钩子(如资源清理)
核心方法有哪些?
TransactionSynchronization
事务同步回调接口,允许在事务的不同生命周期阶段(提交前、提交后、回滚后等)**插入自定义逻辑 **
相当于给事务加了 钩子函数,让我们能在事务关键节点执行额外操作 ,比如提交后发送消息、回滚后清理资源
有哪些核心方法及触发时机?
使用场景有哪些?
事务提交后发送 MQ 消息、更新缓存;
事务回滚后清理临时文件 / 数据;
事务提交前做数据一致性校验;
跨数据源事务的同步(如 MySQL+Redis 的一致性保证)
demo
无异常:事务提交,afterCommit()触发,MQ 消息发送成功;
有异常:事务回滚,afterCommit()不触发,afterCompletion()检测到 STATUS_ROLLED_BACK,执行清理逻辑。
简化写法
@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 与事务底层,从会用到懂原理~~
熟练度刷不停,知识点吃透稳,下期接着练~
版权声明: 本文为 InfoQ 作者【DonaldCen】的原创文章。
原文链接:【http://xie.infoq.cn/article/77c44fc78af7067a32479bb68】。文章转载请联系作者。







评论