day05---Spring AOP

27 阅读3分钟

案例: 操作日志(增删改入库)


这一章要掌握的“主线”

AOP = 把一段重复的通用逻辑(统计耗时 / 记日志 / 权限 / 事务…)抽出来,在“不改业务代码”的前提下,统一织入到指定方法上。

案例主线就是:

Controller 的增删改接口(目标方法)
⬇️ 用自定义注解标记
⬇️ AOP 用 @Around 环绕通知拦截
⬇️ 记录:操作人、时间、类名、方法名、参数、返回值、耗时
⬇️ 写入 operate_log
⬇️ 操作人通过 TokenFilter/Interceptor -> ThreadLocal 传递


1)AOP 核心概念

  • JoinPoint(连接点) :可以被 AOP 织入的点,在 Spring AOP 里主要就是“方法执行”
  • PointCut(切入点) :用表达式/注解筛选哪些方法要增强
  • Advice(通知) :增强逻辑本身(统计耗时 / 记录日志)
  • Aspect(切面) := 通知 + 切入点(“对哪些方法,在什么时候做什么”)
  • Target(目标对象) :被增强的对象(你的 Controller / Service Bean)

image.png


2)通知类型:记住“什么时候会执行”

你用日志案例,必须用 @Around(因为要拿返回值、算耗时)。

  • @Before:目标方法前
  • @After:目标方法后(不管有无异常都执行)
  • @AfterReturning:正常返回后(有异常不执行)
  • @AfterThrowing:抛异常后
  • @Around:前 + 执行目标 + 后(最强)

⚠️ @Around 两个铁律:

  1. 必须手动调用 pjp.proceed(),目标方法才会执行
  2. 返回值必须是 Object,并把 proceed() 的结果返回回去

3)切入点表达式:你现在重点用这两个

A. execution(...)(按方法签名匹配)

适合“方法命名有规律”的场景,比如:

  • execution(* com.itheima.service.*.*(..))
  • execution(* com..DeptServiceImpl.find*(..))

B. @annotation(...)(按注解匹配)

适合“方法名没规律”的场景(增删改就典型没规律),你讲义的方案就是它:

  • 先自定义注解 @LogOperation
  • 想记录日志的方法就打注解
  • 切面用:@Around("@annotation(com.itheima.anno.LogOperation)")

✅ 这就是你案例选择注解切入点的原因。


4)操作日志案例:你要关注的关键细节(很容易踩坑)

4.1 环绕通知要能“异常也记录耗时/日志”

讲义里是“先 proceed(),再记录”,但如果目标方法抛异常:

  • proceed() 后面的代码可能不执行
    👉 建议用 try/finally,保证耗时一定算出来;是否入库看你需求。

4.2 参数/返回值别直接 toString()

  • result.toString() 可能是对象地址,不是你想要的 JSON
  • 参数可能很长,数据库字段有限(method_params 1000、return_value 2000)
    👉 建议用 Jackson 转 JSON,并做长度截断(生产必做)

4.3 操作人怎么拿?

  • 登录后发 JWT
  • 过滤器/拦截器里解析 JWT 得到 id
  • 放 ThreadLocal:CurrentHolder.setCurrentId(id)
  • AOP 中拿:CurrentHolder.getCurrentId()
  • 请求结束必须 remove(),避免线程复用导致串号

5)可以按这个“标准模板”写日志切面(更稳)

核心点:try/catch/finally + JSON + 截断 + 异常也能记录/也能继续抛出

你先理解这个结构就行(不要求你现在背):

  • 记录 start
  • try:执行 proceed,拿 result
  • catch:拿 exception 信息(可选),继续抛出
  • finally:算耗时、拼 OperateLog、insert(插入失败别影响主业务)

6)学习检查:这一章只要能回答这 6 个问题就算会了

  1. 为什么记录操作日志必须用 @Around? ---需要统计耗时
  2. @Around 不写 pjp.proceed() 会怎样?---原始方法不会执行
  3. @AfterReturning@AfterThrowing 的触发条件?---正常返回/抛异常
  4. execution@annotation 各适合什么场景?---适合批量、通用的规则/个性化、特定的需求
  5. 为什么要 ThreadLocal?不用行不行?---为了实现“数据透传”
  6. ThreadLocal 为什么必须 remove?---Tomcat 线程池复用,防止数据串号或者内存泄漏