spring_复习_AOP

核心概念

  • 切面 Aspect:横切逻辑类(日志、事务、权限)
  • 连接点 JoinPoint:可以被拦截的方法
  • 切入点 Pointcut:筛选哪些连接点(表达式 execution(* com.xxx.service.*.*(..))
  • 通知 Advice:切面执行逻辑
    • @Before 前置
    • @AfterReturning 正常返回后
    • @AfterThrowing 异常时
    • @After 最终通知(无论成功失败)
    • @Around 环绕【最强,可以控制目标方法执行】
  • 目标对象 Target:原始被代理类
  • 代理 Proxy:生成的增强对象
  • 织入 Weaving:把切面逻辑植入目标对象(Spring 运行时织入)

Spring AOP 两种代理

  1. JDK 动态代理:目标类实现接口,基于接口代理,只能拦截接口方法
  2. CGLIB 代理:继承目标类生成子类代理,不需要接口

Spring5.x 默认规则:有接口用 JDK,无接口 CGLIB;
SpringBoot 2.x 后默认强制 CGLIB

@Transactional 放在接口上还是实现类上?

推荐标注在实现类 / 实现类方法上,不建议写在接口。

原因:

  1. Spring AOP 代理机制问题
    • JDK 动态代理:基于接口,接口上的注解可以被识别
    • CGLIB 代理:基于子类继承,不会继承接口上的注解 如果切换为 CGLIB 代理,接口上@Transactional直接失效!
  2. 注解继承性:Java 注解默认无法被继承。
  3. 日常开发 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
2
3
4
5
6
7
8
9
@Service
public class UserService {
public void methodA() {
methodB(); // this.methodB(),走原始对象,不走代理
}
@Transactional
public void methodB() {
}
}

原因:使用this调用,绕过 AOP 代理,事务不生效。
解决:注入自身代理、使用 ApplicationContext 获取代理、拆分到不同类。

非 public 方法上加 @Transactional

1
2
@Transactional
private void test(){}

Spring AOP 只会拦截public方法;private/protected/default 无法代理,事务失效。

异常被 try-catch 捕获,没有向外抛出

1
2
3
4
5
6
7
8
@Transactional
public void save(){
try{
//数据库操作
}catch (Exception e){
//吃掉异常,没有throw
}
}

规则:Spring 默认只在抛出运行时异常 (RuntimeException)、Error 触发回滚
异常被捕获,框架感知不到异常,不会回滚。

解决:catch 后重新抛出异常。

抛出受检异常 (Exception),不指定 rollbackFor

1
2
3
4
@Transactional // 默认不回滚Exception
public void test() throws Exception{
throw new Exception();
}

默认规则:仅 RuntimeException / Error 触发回滚。

标准写法:@Transactional(rollbackFor = Exception.class)

传播行为导致不会开启事务 / 不参与事务

  • Propagation.SUPPORTS:有事务就参与,没有就无事务
  • Propagation.NOT_SUPPORTED:挂起当前事务,以非事务运行
  • Propagation.NEVER:如果存在事务直接抛异常
    举例:外层有事务,内层方法配置NOT_SUPPORTED,内层操作不会回滚。

数据库引擎不支持事务

MyISAM 不支持事务;必须使用 InnoDB。

很多新人本地测试库表引擎 MyISAM,事务完全无效。

新线程中执行数据库操作

1
2
3
4
5
6
@Transactional
public void mainMethod(){
new Thread(()->{
//子线程操作DB,新线程独立事务上下文
}).start();
}

事务绑定当前线程 ThreadLocal,子线程无法继承父事务;
子线程异常,无法回滚主线程数据库操作。


spring_复习_AOP
http://hanqichuan.com/2019/09/03/java框架与工具/spring/spring_复习_AOP/
作者
韩启川
发布于
2019年9月3日
许可协议