Spring AOP 解密:从动态代理到声明式事务

前言:上期思考题解答

在上一篇文章的结尾,我们留下了一个思考题:

Spring 的 @Transactional 注解是如何工作的?它和 Bean 的生命周期有什么关系?

现在,经过对 Bean 生命周期的学习,我们可以回答这个问题了:

在 Bean 的初始化后阶段BeanPostProcessorpostProcessAfterInitialization 方法),Spring 会检查 Bean 是否需要事务增强。如果发现 Bean 的方法上有 @Transactional 注解,Spring 就会通过 AOP 为该 Bean 创建一个代理对象,并将原始 Bean 作为目标对象封装起来。当我们调用 Bean 的方法时,实际上是调用代理对象的方法,代理对象会在方法执行前后自动开启、提交或回滚事务。

这正是我们本期要深入探讨的主题——Spring AOP(面向切面编程)


第一部分:为什么需要 AOP?——代码里的"牛皮癣"问题

场景还原:一个简单的订单系统

假设我们有一个订单服务,需要为每个方法添加日志记录和性能监控:

@Service
public class OrderService {
    
    public void createOrder(Order order) {
        // 日志记录
        long startTime = System.currentTimeMillis();
        System.out.println("开始创建订单: " + order.getId());
        
        try {
            // 核心业务逻辑
            // ... 创建订单的代码 ...
            
            // 性能监控
            long cost = System.currentTimeMillis() - startTime;
            System.out.println("创建订单完成,耗时: " + cost + "ms");
        } catch (Exception e) {
            System.out.println("创建订单异常: " + e.getMessage());
            throw e;
        }
    }
    
    public void updateOrder(Order order) {
        // 又是同样的日志和监控代码!
        long startTime = System.currentTimeMillis();
        System.out.println("开始更新订单: " + order.getId());
        
        try {
            // 核心业务逻辑
            // ... 更新订单的代码 ...
            
            long cost = System.currentTimeMillis() - startTime;
            System.out.println("更新订单完成,耗时: " + cost + "ms");
        } catch (Exception e) {
            System.out.println("更新订单异常: " + e.getMessage());
            throw e;
        }
    }
    
    public void deleteOrder(Long orderId) {
        // 再次重复...
        long startTime = System.currentTimeMillis();
        System.out.println("开始删除订单: " + orderId);
        
        try {
            // 核心业务逻辑
            // ... 删除订单的代码 ...
            
            long cost = System.currentTimeMillis() - startTime;
            System.out.println("删除订单完成,耗时: " + cost + "ms");
        } catch (Exception e) {
            System.out.println("删除订单异常: " + e.getMessage());
            throw e;
        }
    }
}

问题的本质

这种代码带来了几个严重问题:

  1. 代码重复:每个方法都要写相同的日志和监控代码
  2. 关注点混杂:核心业务逻辑与非业务逻辑(日志、监控)混杂在一起
  3. 维护困难:如果想修改日志格式,需要改动所有方法
  4. 可测试性差:测试业务逻辑时,还要考虑日志代码的影响

这就是典型的**横切关注点(Cross-cutting Concerns)**问题——那些散布在系统各处的、与核心业务无关的通用功能。

横切关注点的例子

关注点 说明 传统实现的问题
日志记录 记录方法调用信息 每个方法都要写日志代码
事务管理 开启/提交/回滚事务 业务代码被事务代码淹没
安全控制 权限校验 每个需要权限的方法都要校验
性能监控 统计方法执行时间 重复的计时代码
缓存处理 查询缓存/更新缓存 缓存逻辑与业务逻辑耦合

AOP 正是为解决这些问题而生。


第二部分:AOP 核心概念——一场舞台剧的比喻

理解 AOP 的概念最好的方式,是把它想象成一场舞台剧。

舞台剧的比喻

AOP 概念 舞台剧比喻 技术含义
切面(Aspect) 导演 封装横切关注点的模块
连接点(Join Point) 可以加戏的位置 程序执行过程中的可插入点(如方法调用)
切点(Pointcut) 具体在哪些场景加戏 匹配连接点的表达式
通知(Advice) 加的戏是什么 在连接点执行的操作
目标对象(Target) 演员 被切面增强的原始对象
代理对象(Proxy) 替身 为目标对象创建的代理实例
织入(Weaving) 排练并正式演出 将切面应用到目标对象的过程

核心概念详解

1. 切面(Aspect)

切面是切点通知的集合,定义了"在哪里"和"做什么"。

@Aspect  // 告诉 Spring:这是一个切面
@Component
public class LoggingAspect {
    // 切点 + 通知
}
2. 连接点(Join Point)

在 Spring AOP 中,连接点特指方法的执行。当方法被调用时,就是一个连接点。

3. 切点(Pointcut)

切点通过表达式来定位哪些方法需要被增强:

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
4. 通知(Advice)

通知定义了增强的具体逻辑,Spring 支持 5 种通知类型:

通知类型 注解 执行时机 比喻
前置通知 @Before 目标方法执行前 演员上台前,导演喊"准备"
后置通知 @After 目标方法执行后(无论成功/异常) 演员下台后,无论演得如何
返回通知 @AfterReturning 目标方法成功返回后 演员完美谢幕
异常通知 @AfterThrowing 目标方法抛出异常后 演员忘词了,导演救场
环绕通知 @Around 包围目标方法 导演全程把控,随时喊停或改戏

第三部分:AOP 的底层实现——动态代理的秘密

Spring AOP 的底层实现基于代理模式,具体有两种方式:JDK 动态代理CGLIB 动态代理

3.1 代理模式基础

在深入动态代理之前,我们先理解什么是代理模式。

静态代理示例
// 1. 抽象主题
public interface HouseSubject {
    void saleHouse();
    void rentHouse();
}

// 2. 真实主题(业主)
public class RealHouseSubject implements HouseSubject {
    @Override
    public void saleHouse() {
        System.out.println("业主执行房屋出售流程");
    }
    
    @Override
    public void rentHouse() {
        System.out.println("业主执行房屋租赁流程");
    }
}

// 3. 代理类(中介)
public class HouseProxy implements HouseSubject {
    private HouseSubject realSubject;
    
    public HouseProxy(HouseSubject realSubject) {
        this.realSubject = realSubject;
    }
    
    @Override
    public void saleHouse() {
        System.out.println("[中介] 审核房源、发布信息");  // 前置增强
        realSubject.saleHouse();                         // 调用真实方法
        System.out.println("[中介] 协助过户、归档");      // 后置增强
    }
    
    @Override
    public void rentHouse() {
        System.out.println("[中介] 核实租客身份");
        realSubject.rentHouse();
        System.out.println("[中介] 定期巡检");
    }
}

// 使用
RealHouseSubject owner = new RealHouseSubject();
HouseProxy proxy = new HouseProxy(owner);
proxy.saleHouse();  // 通过中介买房

静态代理的缺点:

  • 每个类都需要手动编写代理类
  • 如果接口新增方法,代理类也必须同步修改
  • 类爆炸:N 个业务类就需要 N 个代理类

3.2 JDK 动态代理(基于接口)

JDK 动态代理是 Java 原生支持的代理机制,要求目标对象必须实现至少一个接口

实现原理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 定义接口(必须)
public interface UserService {
    User getUserById(Long id);
}

// 2. 实现类
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(Long id) {
        System.out.println("查询用户: " + id);
        return new User(id, "张三");
    }
}

// 3. InvocationHandler(增强逻辑)
public class LogInvocationHandler implements InvocationHandler {
    private final Object target;  // 目标对象
    
    public LogInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强
        System.out.println("方法执行前: " + method.getName());
        
        // 调用目标方法
        Object result = method.invoke(target, args);
        
        // 后置增强
        System.out.println("方法执行后: " + method.getName());
        
        return result;
    }
    
    // 创建代理对象的静态方法
    public static Object createProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),     // 类加载器
            target.getClass().getInterfaces(),      // 目标实现的接口
            new LogInvocationHandler(target)        // InvocationHandler
        );
    }
}

// 4. 使用
UserService target = new UserServiceImpl();
UserService proxy = (UserService) LogInvocationHandler.createProxy(target);
proxy.getUserById(1L);

JDK 动态代理的核心Proxy.newProxyInstance() 在运行时动态生成一个代理类,这个代理类实现了目标接口,并将所有方法调用转发给 InvocationHandlerinvoke 方法。

3.3 CGLIB 动态代理(基于继承)

当目标类没有实现任何接口时,Spring 会采用 CGLIB 来创建代理。CGLIB 通过继承目标类来生成子类作为代理。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 1. 目标类(没有接口)
public class UserDao {
    public User getUserById(Long id) {
        System.out.println("查询用户: " + id);
        return new User(id, "李四");
    }
}

// 2. MethodInterceptor(增强逻辑)
public class LogMethodInterceptor implements MethodInterceptor {
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, 
                            MethodProxy proxy) throws Throwable {
        // 前置增强
        System.out.println("CGLIB: 方法执行前 " + method.getName());
        
        // 调用父类方法(注意这里是 proxy.invokeSuper)
        Object result = proxy.invokeSuper(obj, args);
        
        // 后置增强
        System.out.println("CGLIB: 方法执行后 " + method.getName());
        
        return result;
    }
    
    // 创建代理对象
    public static Object createProxy(Class<?> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);           // 设置父类
        enhancer.setCallback(new LogMethodInterceptor()); // 设置回调
        return enhancer.create();                      // 创建代理
    }
}

// 3. 使用
UserDao proxy = (UserDao) LogMethodInterceptor.createProxy(UserDao.class);
proxy.getUserById(1L);

CGLIB 的原理:通过字节码技术生成目标类的子类,并重写所有非 final 的方法,在重写的方法中插入增强逻辑。

3.4 JDK 动态代理 vs CGLIB 对比

对比维度 JDK 动态代理 CGLIB 动态代理
实现方式 基于接口,生成接口实现类 基于继承,生成子类
目标要求 必须实现至少一个接口 无接口要求
final 方法 无法代理(接口方法默认不是 final) 无法代理 final 方法
性能 代理对象创建快,执行略慢 代理对象创建慢,执行略快
Spring 默认策略 目标有接口时使用 目标无接口时使用
Spring Boot 2.0+ 可配置,但默认 CGLIB 默认使用

3.5 Spring 的代理选择策略

// Spring 的核心选择逻辑(简化版)
public class DefaultAopProxyFactory {
    
    public AopProxy createAopProxy(AdvisedSupport config) {
        // 是否有 optimize 配置?是否 proxy-target-class=true?是否有接口?
        if (config.isOptimize() || config.isProxyTargetClass() || 
            hasNoUserSuppliedProxyInterfaces(config)) {
            // 使用 CGLIB 代理
            return new CglibAopProxy(config);
        } else {
            // 使用 JDK 动态代理
            return new JdkDynamicAopProxy(config);
        }
    }
    
    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        Class<?>[] interfaces = config.getProxiedInterfaces();
        return interfaces.length == 0 || 
               (interfaces.length == 1 && 
                SpringProxy.class.equals(interfaces[0]));
    }
}

在 Spring Boot 2.0 之后,默认使用 CGLIB 代理,即使目标类有接口。可以通过配置切换:

# application.properties
spring.aop.proxy-target-class=false  # 切换回 JDK 代理

第四部分:Spring AOP 实战——5 种通知类型详解

4.1 环境准备

<!-- Maven 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
// 启用 AOP(Spring Boot 自动配置,通常不需要手动加)
@SpringBootApplication
@EnableAspectJAutoProxy  // 如果需要显式开启
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4.2 切点表达式详解

切点表达式是 AOP 的核心,它告诉 Spring 哪些方法需要被拦截。

execution 表达式(最常用)
execution(修饰符 返回值 包名.类名.方法名(参数) 异常)
// 匹配指定包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}

// 匹配指定类的所有 public 方法
@Pointcut("execution(public * com.example.service.UserService.*(..))")
public void userServiceMethods() {}

// 匹配指定方法(精确匹配)
@Pointcut("execution(* com.example.service.OrderService.createOrder(Long, String))")
public void createOrderMethod() {}

// 匹配所有 save 开头的方法
@Pointcut("execution(* com.example.service.*.save*(..))")
public void saveMethods() {}
其他切点表达式
// 匹配标注了特定注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}

// 匹配标注了特定注解的类中的所有方法
@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceClassMethods() {}

// 匹配指定参数类型的方法
@Pointcut("args(Long, String)")
public void methodsWithLongAndStringArgs() {}

// 匹配 Bean 名称
@Pointcut("bean(userService)")
public void userServiceBean() {}

4.3 五种通知类型实战

我们通过一个完整的日志切面来演示所有通知类型。

@Aspect
@Component
@Slf4j
public class ComprehensiveLoggingAspect {
    
    /**
     * 定义切点:匹配 service 包下所有类的所有方法
     */
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    /**
     * 1. 前置通知 @Before
     * 在目标方法执行前执行
     */
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("【@Before】方法 {} 开始执行,参数:{}", methodName, Arrays.toString(args));
    }
    
    /**
     * 2. 后置通知 @After
     * 在目标方法执行后执行(无论成功还是异常)
     */
    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("【@After】方法 {} 执行结束(finally)", methodName);
    }
    
    /**
     * 3. 返回通知 @AfterReturning
     * 在目标方法成功返回后执行,可以获取返回值
     */
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("【@AfterReturning】方法 {} 执行成功,返回值:{}", methodName, result);
    }
    
    /**
     * 4. 异常通知 @AfterThrowing
     * 在目标方法抛出异常时执行,可以获取异常信息
     */
    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        log.error("【@AfterThrowing】方法 {} 执行异常,异常信息:{}", methodName, ex.getMessage());
    }
    
    /**
     * 5. 环绕通知 @Around(功能最强大)
     * 可以控制目标方法的执行时机,修改参数和返回值
     */
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        
        // 前置处理
        long startTime = System.currentTimeMillis();
        log.info("【@Around】方法 {} 开始执行,参数:{}", methodName, Arrays.toString(args));
        
        try {
            // 执行目标方法(关键!必须调用 proceed)
            Object result = pjp.proceed();
            
            // 后置处理(成功)
            long cost = System.currentTimeMillis() - startTime;
            log.info("【@Around】方法 {} 执行成功,耗时:{}ms,返回值:{}", 
                     methodName, cost, result);
            
            // 可以修改返回值
            return result;
            
        } catch (IllegalArgumentException e) {
            // 异常处理
            log.error("【@Around】方法 {} 参数异常:{}", methodName, e.getMessage());
            
            // 可以抛出原异常,也可以返回默认值
            throw e;  // 继续抛出
        }
    }
}

4.4 获取 JoinPoint 信息

JoinPoint 对象提供了丰富的 API 来获取方法信息:

@Before("serviceMethods()")
public void logDetails(JoinPoint joinPoint) {
    // 1. 方法签名
    Signature signature = joinPoint.getSignature();
    String methodName = signature.getName();           // 方法名
    String className = signature.getDeclaringTypeName(); // 类名
    
    // 2. 方法参数
    Object[] args = joinPoint.getArgs();               // 参数数组
    
    // 3. 目标对象
    Object target = joinPoint.getTarget();              // 目标对象
    Object proxy = joinPoint.getThis();                  // 代理对象
    
    // 4. 获取注解(如果有)
    MethodSignature methodSignature = (MethodSignature) signature;
    Method method = methodSignature.getMethod();
    Transactional transactional = method.getAnnotation(Transactional.class);
    
    log.info("类:{},方法:{},参数:{}", className, methodName, args);
}

4.5 多个切面的执行顺序

当有多个切面时,可以通过 @Order 注解控制执行顺序:

@Aspect
@Component
@Order(1)  // 数值越小,优先级越高
public class LoggingAspect {
    // ...
}

@Aspect
@Component
@Order(2)
public class TransactionAspect {
    // ...
}

执行顺序规则:

  • 目标方法执行前:按 @Order 值从小到大执行
  • 目标方法执行后:按 @Order 值从大到小执行
执行顺序:
@Order(1) @Before
    @Order(2) @Before
        目标方法执行
    @Order(2) @After
@Order(1) @After

第五部分:源码级解读——Spring AOP 是如何工作的?

5.1 核心组件

Spring AOP 的核心组件包括:

组件 作用
ProxyFactory 创建代理对象的工厂
Advice 通知(增强逻辑)
Advisor 包含 Advice 和 Pointcut 的完整切面单元
Pointcut 定义哪些方法需要被拦截
AopProxy 代理对象的抽象(JdkDynamicAopProxy / CglibAopProxy)

5.2 代理创建流程

// 简化版的代理创建流程
public class ProxyFactory {
    
    public Object getProxy() {
        // 1. 创建 AopProxy(根据配置选择 JDK 或 CGLIB)
        AopProxy aopProxy = createAopProxy();
        
        // 2. 获取代理对象
        return aopProxy.getProxy();
    }
    
    private AopProxy createAopProxy() {
        if (useJdkProxy()) {
            return new JdkDynamicAopProxy(this);
        } else {
            return new CglibAopProxy(this);
        }
    }
}

5.3 JDK 动态代理的实现源码

// JdkDynamicAopProxy 实现了 InvocationHandler 和 AopProxy
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    
    private final AdvisedSupport advised;  // 包含所有配置
    
    @Override
    public Object getProxy() {
        // 生成代理对象
        return Proxy.newProxyInstance(
            advised.getClassLoader(),
            advised.getProxiedInterfaces(),
            this  // InvocationHandler
        );
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取拦截器链(所有需要执行的 Advice)
        List<Object> chain = advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        
        if (chain.isEmpty()) {
            // 没有拦截器,直接调用目标方法
            return method.invoke(target, args);
        }
        
        // 创建方法调用对象
        MethodInvocation invocation = new ReflectiveMethodInvocation(
            proxy, target, method, args, targetClass, chain);
        
        // 执行拦截器链(递归调用)
        return invocation.proceed();
    }
}

5.4 拦截器链的执行过程

// ReflectiveMethodInvocation.proceed() 简化版
public Object proceed() throws Throwable {
    // 当前拦截器索引
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        // 所有拦截器执行完毕,调用目标方法
        return invokeJoinpoint();
    }
    
    // 获取下一个拦截器
    Object interceptorOrInterceptionAdvice = 
        this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    
    if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {
        // 执行拦截器
        MethodInterceptor mi = (MethodInterceptor) interceptorOrInterceptionAdvice;
        return mi.invoke(this);  // 注意传入 this,实现递归
    } else {
        // 其他类型,直接 proceed
        return proceed();
    }
}

5.5 完整的 AOP 调用链

调用 proxy.doSomething()
    ↓
JdkDynamicAopProxy.invoke()
    ↓
获取拦截器链 [ExposeInvocationInterceptor, BeforeAdvice, AfterReturningAdvice]
    ↓
ReflectiveMethodInvocation.proceed()
    ↓
├─ 拦截器1: ExposeInvocationInterceptor.invoke(this) 
│      ↓
│   ReflectiveMethodInvocation.proceed()
│      ↓
│   ├─ 拦截器2: MethodBeforeAdviceInterceptor.invoke(this)
│   │      ↓ 执行 @Before 逻辑
│   │   ReflectiveMethodInvocation.proceed()
│   │      ↓
│   │   ├─ 拦截器3: AfterReturningAdviceInterceptor.invoke(this)
│   │   │      ↓
│   │   │   ReflectiveMethodInvocation.proceed()
│   │   │      ↓
│   │   │   └─ 目标方法执行(无更多拦截器)
│   │   │      ↓ 返回结果
│   │   └─ 执行 @AfterReturning 逻辑
│   └─ 返回结果
└─ 返回最终结果

第六部分:声明式事务——AOP 的最佳实践

理解了 AOP 的原理,我们再来看 Spring 声明式事务,就豁然开朗了。

6.1 @Transactional 的工作原理

@Transactional 本质上就是一个基于 AOP 实现的事务管理切面

// 事务管理的核心拦截器
public class TransactionInterceptor implements MethodInterceptor {
    
    private PlatformTransactionManager transactionManager;
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 1. 获取事务属性(从 @Transactional 解析)
        TransactionAttribute txAttr = getTransactionAttribute(invocation);
        
        // 2. 获取事务管理器
        PlatformTransactionManager tm = determineTransactionManager(txAttr);
        
        // 3. 创建事务(如果必要)
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, invocation);
        
        Object retVal = null;
        try {
            // 4. 执行目标方法
            retVal = invocation.proceed();
        } catch (Throwable ex) {
            // 5. 异常时回滚事务
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            // 6. 清理事务信息
            cleanupTransactionInfo(txInfo);
        }
        
        // 7. 正常提交事务
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
}

6.2 事务管理器的核心流程

// DataSourceTransactionManager 简化版
public class DataSourceTransactionManager implements PlatformTransactionManager {
    
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        Connection con = txObject.getConnectionHolder().getConnection();
        
        // 设置事务属性
        con.setAutoCommit(false);  // 关闭自动提交
        con.setTransactionIsolation(definition.getIsolationLevel());
        
        // 将连接绑定到当前线程
        TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
    }
    
    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        con.commit();  // 提交事务
    }
    
    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        con.rollback();  // 回滚事务
    }
}

6.3 事务传播行为

@Transactionalpropagation 属性定义了事务的传播行为:

传播行为 说明
REQUIRED(默认) 支持当前事务,如果没有则创建新事务
REQUIRES_NEW 创建新事务,挂起当前事务
SUPPORTS 支持当前事务,如果没有则以非事务方式执行
NOT_SUPPORTED 以非事务方式执行,挂起当前事务
MANDATORY 必须在一个事务中运行,否则抛异常
NEVER 必须不在事务中运行,否则抛异常
NESTED 如果存在当前事务,则在嵌套事务中执行

6.4 常见陷阱与最佳实践

陷阱1:同一个类中的方法调用导致事务失效
@Service
public class UserService {
    
    @Transactional
    public void methodA() {
        // 有事务
        methodB();  // 直接调用,事务失效!
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 本应有新事务,但因为是内部调用,没有经过代理
    }
}

原因:内部方法调用是通过 this 调用的,没有经过代理对象,所以 AOP 拦截器不会执行。

解决方案:通过代理对象调用

@Service
public class UserService {
    
    @Autowired
    private UserService self;  // 注入自身代理
    
    @Transactional
    public void methodA() {
        self.methodB();  // 通过代理调用
    }
}
陷阱2:事务回滚只对 RuntimeException 生效
@Transactional
public void method() throws Exception {
    // 业务逻辑
    throw new Exception("checked exception");  // 不会回滚!
}

默认回滚策略:只对 RuntimeExceptionError 回滚。

解决方案:指定回滚异常

@Transactional(rollbackFor = Exception.class)
public void method() throws Exception {
    throw new Exception("checked exception");  // 现在会回滚
}
陷阱3:事务超时设置
@Transactional(timeout = 5)  // 5秒超时
public void longRunningMethod() {
    // 如果执行超过5秒,事务将自动回滚
}

第七部分:总结与下期预告

本节核心要点

知识点 核心内容 应用场景
AOP 概念 切面、切点、通知、连接点 理解 AOP 的基本术语
动态代理 JDK(接口) vs CGLIB(继承) 理解 Spring AOP 底层
5 种通知 @Before、@After、@Around 等 实现各种增强逻辑
切点表达式 execution、@annotation、args 精确定位目标方法
声明式事务 @Transactional 的工作原理 理解事务的底层机制

一图胜千言:Spring AOP 全景图

┌─────────────────────────────────────────────────────────────┐
│                     Spring AOP 全景图                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  【配置阶段】                                                 │
│  @Aspect + @Component → 切面定义                              │
│  @Pointcut → 切点表达式                                       │
│  @Before/@After/@Around → 通知类型                            │
│         ↓                                                    │
│  【容器启动阶段】                                              │
│  BeanPostProcessor 扫描 @Aspect                              │
│         ↓                                                    │
│  【代理创建阶段】                                              │
│  ProxyFactory → 选择代理方式                                   │
│  ├─ 有接口且 proxy-target-class=false → JDK 动态代理          │
│  └─ 无接口或 proxy-target-class=true → CGLIB 代理             │
│         ↓                                                    │
│  【方法调用阶段】                                              │
│  调用代理对象方法 → InvocationHandler/MethodInterceptor       │
│         ↓                                                    │
│  执行拦截器链 → 依次执行各通知                                  │
│         ↓                                                    │
│  最终调用目标方法                                              │
│                                                              │
└─────────────────────────────────────────────────────────────┘

一个小思考题

Spring 的 @Async 注解是如何实现异步调用的?它和 @Transactional 的实现原理有什么异同?

提示:想想 @Async 也是通过 AOP 实现的,但它的拦截器做了什么不同的事情?

下期预告

《Spring 全家桶全景——从核心到生态的进阶之路》

我们将梳理 Spring 生态的各个模块:Spring MVC、Spring Boot、Spring Cloud、Spring Data 等,为你绘制一张完整的学习地图。


写在最后

通过这三篇文章,我们从 IoC 容器出发,深入 Bean 的生命周期,再到 AOP 的动态代理原理,最后落地到声明式事务——已经完整地走过了 Spring 框架的核心脉络。

现在你应该能理解:

  • IoC:把对象的创建权交给容器
  • DI:容器负责建立依赖关系
  • Bean 生命周期:对象从生到死的完整历程
  • AOP:通过动态代理实现横切关注点的分离
  • 声明式事务:AOP 在事务管理中的经典应用

Spring 的"魔法"不再神秘,它是一套精心设计的、帮助我们管理对象和分离关注点的框架。

如果你觉得这个系列对你有帮助,欢迎点赞、收藏、分享。有任何疑问,请在评论区留言,我们一起讨论。

下期见!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐