从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(3)
Spring AOP 解密:从动态代理到声明式事务
前言:上期思考题解答
在上一篇文章的结尾,我们留下了一个思考题:
Spring 的 @Transactional 注解是如何工作的?它和 Bean 的生命周期有什么关系?
现在,经过对 Bean 生命周期的学习,我们可以回答这个问题了:
在 Bean 的初始化后阶段(BeanPostProcessor 的 postProcessAfterInitialization 方法),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;
}
}
}
问题的本质
这种代码带来了几个严重问题:
- 代码重复:每个方法都要写相同的日志和监控代码
- 关注点混杂:核心业务逻辑与非业务逻辑(日志、监控)混杂在一起
- 维护困难:如果想修改日志格式,需要改动所有方法
- 可测试性差:测试业务逻辑时,还要考虑日志代码的影响
这就是典型的**横切关注点(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() 在运行时动态生成一个代理类,这个代理类实现了目标接口,并将所有方法调用转发给 InvocationHandler 的 invoke 方法。
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 事务传播行为
@Transactional 的 propagation 属性定义了事务的传播行为:
| 传播行为 | 说明 |
|---|---|
| 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"); // 不会回滚!
}
默认回滚策略:只对 RuntimeException 和 Error 回滚。
解决方案:指定回滚异常
@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 的"魔法"不再神秘,它是一套精心设计的、帮助我们管理对象和分离关注点的框架。
如果你觉得这个系列对你有帮助,欢迎点赞、收藏、分享。有任何疑问,请在评论区留言,我们一起讨论。
下期见!
更多推荐




所有评论(0)