spring_复习_AOP
核心概念
- 切面 Aspect:横切逻辑类(日志、事务、权限)
- 连接点 JoinPoint:可以被拦截的方法
- 切入点 Pointcut:筛选哪些连接点(表达式
execution(* com.xxx.service.*.*(..))) - 通知 Advice:切面执行逻辑
- @Before 前置
- @AfterReturning 正常返回后
- @AfterThrowing 异常时
- @After 最终通知(无论成功失败)
- @Around 环绕【最强,可以控制目标方法执行】
- 目标对象 Target:原始被代理类
- 代理 Proxy:生成的增强对象
- 织入 Weaving:把切面逻辑植入目标对象(Spring 运行时织入)
Spring AOP 两种代理
- JDK 动态代理:目标类实现接口,基于接口代理,只能拦截接口方法
- CGLIB 代理:继承目标类生成子类代理,不需要接口
Spring5.x 默认规则:有接口用 JDK,无接口 CGLIB;
SpringBoot 2.x 后默认强制 CGLIB
@Transactional 放在接口上还是实现类上?
推荐标注在实现类 / 实现类方法上,不建议写在接口。
原因:
- Spring AOP 代理机制问题
- JDK 动态代理:基于接口,接口上的注解可以被识别
- CGLIB 代理:基于子类继承,不会继承接口上的注解 如果切换为 CGLIB 代理,接口上
@Transactional直接失效!
- 注解继承性:Java 注解默认无法被继承。
- 日常开发 SpringBoot 默认使用 CGLIB,极易出现事务失效 bug。
规范:事务注解直接写在实现类方法。
应用场景
AOP 核心:把通用逻辑抽离,不侵入业务代码,横向切面。
1. 日志处理(最常用)
- 接口入参、出参打印
- 方法执行耗时统计
- 异常日志捕获记录
不用每个方法里写 log 代码
2. 统一异常处理
捕获业务方法抛出异常,统一封装返回结果、告警。
注意:全局 @RestControllerAdvice 是 Web 层 AOP 思想;Spring AOP 可拦截 Service 层异常。
3. 权限校验 / 身份认证
执行方法前校验:登录状态、角色、接口访问权限。
如自定义注解
@RequirePermission("admin"),切面拦截校验。
4. 事务管理(Spring 内置)
@Transactional 底层就是 AOP 实现。
拦截方法,方法成功提交事务,异常回滚。
5. 接口限流、防重复提交
- 通过注解标记接口,切面内:
Redis 判断是否重复请求、限流计数,超限直接拦截。
6. 数据缓存
方法执行前先查缓存;无缓存执行方法,结果放入缓存。
类似简易缓存封装(很多自研缓存框架基于 AOP)。
7. 参数校验、参数脱敏
- 执行方法前校验参数合法性
- 返回数据自动脱敏:手机号、身份证、银行卡打码
8. 操作记录(审计日志)
记录谁、什么时候、操作了什么功能,保存操作日志。
自定义注解 @OperateLog("修改用户信息")。
9. 性能监控
统计每个方法调用频次、平均耗时,用于系统性能分析。
10. 分布式锁封装
自定义注解 @DistributedLock,切面自动获取 / 释放 Redis 锁。
事务失效场景
同类内部方法调用
1 | |
原因:使用this调用,绕过 AOP 代理,事务不生效。
解决:注入自身代理、使用 ApplicationContext 获取代理、拆分到不同类。
非 public 方法上加 @Transactional
1 | |
Spring AOP 只会拦截public方法;private/protected/default 无法代理,事务失效。
异常被 try-catch 捕获,没有向外抛出
1 | |
规则:Spring 默认只在抛出运行时异常 (RuntimeException)、Error 触发回滚;
异常被捕获,框架感知不到异常,不会回滚。
解决:catch 后重新抛出异常。
抛出受检异常 (Exception),不指定 rollbackFor
1 | |
默认规则:仅 RuntimeException / Error 触发回滚。
标准写法:@Transactional(rollbackFor = Exception.class)
传播行为导致不会开启事务 / 不参与事务
Propagation.SUPPORTS:有事务就参与,没有就无事务Propagation.NOT_SUPPORTED:挂起当前事务,以非事务运行Propagation.NEVER:如果存在事务直接抛异常
举例:外层有事务,内层方法配置NOT_SUPPORTED,内层操作不会回滚。
数据库引擎不支持事务
MyISAM 不支持事务;必须使用 InnoDB。
很多新人本地测试库表引擎 MyISAM,事务完全无效。
新线程中执行数据库操作
1 | |
事务绑定当前线程 ThreadLocal,子线程无法继承父事务;
子线程异常,无法回滚主线程数据库操作。