案例: 操作日志(增删改入库) 。
这一章要掌握的“主线”
AOP = 把一段重复的通用逻辑(统计耗时 / 记日志 / 权限 / 事务…)抽出来,在“不改业务代码”的前提下,统一织入到指定方法上。
案例主线就是:
Controller 的增删改接口(目标方法)
⬇️ 用自定义注解标记
⬇️ AOP 用@Around环绕通知拦截
⬇️ 记录:操作人、时间、类名、方法名、参数、返回值、耗时
⬇️ 写入operate_log表
⬇️ 操作人通过TokenFilter/Interceptor -> ThreadLocal传递
1)AOP 核心概念
- JoinPoint(连接点) :可以被 AOP 织入的点,在 Spring AOP 里主要就是“方法执行”
- PointCut(切入点) :用表达式/注解筛选哪些方法要增强
- Advice(通知) :增强逻辑本身(统计耗时 / 记录日志)
- Aspect(切面) := 通知 + 切入点(“对哪些方法,在什么时候做什么”)
- Target(目标对象) :被增强的对象(你的 Controller / Service Bean)
2)通知类型:记住“什么时候会执行”
你用日志案例,必须用 @Around(因为要拿返回值、算耗时)。
@Before:目标方法前@After:目标方法后(不管有无异常都执行)@AfterReturning:正常返回后(有异常不执行)@AfterThrowing:抛异常后@Around:前 + 执行目标 + 后(最强)
⚠️ @Around 两个铁律:
- 必须手动调用
pjp.proceed(),目标方法才会执行 - 返回值必须是
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 个问题就算会了
- 为什么记录操作日志必须用
@Around? ---需要统计耗时 @Around不写pjp.proceed()会怎样?---原始方法不会执行@AfterReturning和@AfterThrowing的触发条件?---正常返回/抛异常execution和@annotation各适合什么场景?---适合批量、通用的规则/个性化、特定的需求- 为什么要 ThreadLocal?不用行不行?---为了实现“数据透传”
- ThreadLocal 为什么必须 remove?---Tomcat 线程池复用,防止数据串号或者内存泄漏