Spring 如何使用 AOP 实现声明式事务管理?

Spring 使用 AOP 实现声明式事务管理是其最强大的功能之一。下面我将详细拆解这个过程,从高层概念到底层原理。


一、 核心思想:代理模式 + AOP

想象一下,你是一位非常重要的业务专家(你的 Service 层代码),你的工作是处理核心业务逻辑(比如转账、更新用户信息)。但每次处理业务前,你都需要做一些准备工作(开启事务),结束后还要做一些收尾工作(提交或回滚事务)。这些准备和收尾工作繁琐、重复,而且和你的核心业务关系不大。

声明式事务管理的目标就是:让你这位专家只专注于核心业务,把所有繁琐的事务管理工作交给一个“管家”来处理。

这个“管家”就是 Spring AOP 创建的代理对象 (Proxy)

AOP (Aspect-Oriented Programming, 面向切面编程) 在这里扮演的角色就是:

  1. 定义“切面” (Aspect):将事务管理(开启、提交、回滚)这个“横切关注点”从业务代码中抽离出来,形成一个独立的模块。
  2. 定义“切点” (Pointcut):精确地定义这个“管家”应该在哪些方法执行前后介入。在 Spring 中,这通常是通过 @Transactional 注解来指定的。
  3. 创建“代理” (Proxy):Spring 不会直接返回你的原始业务对象,而是会创建一个该对象的代理。所有对业务对象的调用都会先经过这个代理。

二、 详细工作流程(一步步拆解)

让我们跟踪一个对 @Transactional 方法的调用,看看 Spring 内部到底发生了什么。

场景:一个 UserController 调用一个 UserServiceupdateUser() 方法,该方法被 @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 方法内部抛出了一个异常(默认是 RuntimeExceptionError)。
    • 拦截器在其 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 代理是通过继承来实现的,而 privateprotected 方法无法被子类重写(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 完美地将业务逻辑和非功能性的事务管理代码解耦,让开发者能更专注于业务价值的实现,代码也变得更加清晰和易于维护。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值