分享我在实习中第一次接触 AOP 切面技术
------一个 Java 实习生的 AOP 入门实战笔记
一、开场:从"我以为我懂 AOP"说起
我第一次真正接触 AOP,是实习第三周拿到的一个工单。
导师在群里 @我:
"给那个导出按钮加个防抖,别让用户连点 10 次把后端打挂。"
我心想:防抖?加个 setTimeout 不就行了?
导师又说:
"加个
@DistributedLock就行,注解直接打在方法上。"
我当时的表情大概是:
@DistributedLock?这啥?- 是
@Lock吗?Java 有原生 Lock 啊? - 等等,"注解打在方法上"------AOP?
我 ctrl+click 跳到那个注解,发现就是一个 5 行的自定义注解;继续 ctrl+click 找"谁在处理这个注解",跳到了一个 200 行的切面类。
那一刻我整个人是懵的。
SpEL 是什么?UUID 当 owner 是为啥?Lua 脚本又是干嘛的?
后来我花了 2 天才把那个切面彻底看懂,又花了一周自己写了一个 @Log 切面。这篇博客,就是把那段"从懵到会"的过程完整复盘。
一句话核心观点 :AOP 不是"装酷"的设计模式,它就是"把重复代码抽出来"的一种工程化手段。
二、概念扫盲:AOP 到底是个啥?
2.1 先讲清楚"为什么要 AOP"
在聊 AOP 之前,先看一段我实习前写的"用户导出 Excel"代码------这是新手最常写的版本:
java
public void exportExcel(UserQuery query) {
log.info("导出开始,用户:{}", getCurrentUser());
long start = System.currentTimeMillis();
try {
// ① 加分布式锁防重复点击
if (!redisLock.tryLock("export:lock", 30)) {
return fail("操作太频繁");
}
// ② 记录操作日志
auditLog.info("用户 {} 导出 Excel,参数:{}", getCurrentUser(), query);
// ③ 业务逻辑
List<User> list = userDao.list(query);
ExcelUtil.write(list);
// ④ 记录耗时
log.info("导出完成,耗时:{}ms", System.currentTimeMillis() - start);
} finally {
redisLock.unlock("export:lock");
}
}
这代码看着"功能完整"对吧?但它有三个要命的痛点:
#mermaid-svg-QS8jZAJDYMx3Yuuy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QS8jZAJDYMx3Yuuy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QS8jZAJDYMx3Yuuy .error-icon{fill:#552222;}#mermaid-svg-QS8jZAJDYMx3Yuuy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QS8jZAJDYMx3Yuuy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QS8jZAJDYMx3Yuuy .marker.cross{stroke:#333333;}#mermaid-svg-QS8jZAJDYMx3Yuuy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QS8jZAJDYMx3Yuuy p{margin:0;}#mermaid-svg-QS8jZAJDYMx3Yuuy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QS8jZAJDYMx3Yuuy .cluster-label text{fill:#333;}#mermaid-svg-QS8jZAJDYMx3Yuuy .cluster-label span{color:#333;}#mermaid-svg-QS8jZAJDYMx3Yuuy .cluster-label span p{background-color:transparent;}#mermaid-svg-QS8jZAJDYMx3Yuuy .label text,#mermaid-svg-QS8jZAJDYMx3Yuuy span{fill:#333;color:#333;}#mermaid-svg-QS8jZAJDYMx3Yuuy .node rect,#mermaid-svg-QS8jZAJDYMx3Yuuy .node circle,#mermaid-svg-QS8jZAJDYMx3Yuuy .node ellipse,#mermaid-svg-QS8jZAJDYMx3Yuuy .node polygon,#mermaid-svg-QS8jZAJDYMx3Yuuy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QS8jZAJDYMx3Yuuy .rough-node .label text,#mermaid-svg-QS8jZAJDYMx3Yuuy .node .label text,#mermaid-svg-QS8jZAJDYMx3Yuuy .image-shape .label,#mermaid-svg-QS8jZAJDYMx3Yuuy .icon-shape .label{text-anchor:middle;}#mermaid-svg-QS8jZAJDYMx3Yuuy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QS8jZAJDYMx3Yuuy .rough-node .label,#mermaid-svg-QS8jZAJDYMx3Yuuy .node .label,#mermaid-svg-QS8jZAJDYMx3Yuuy .image-shape .label,#mermaid-svg-QS8jZAJDYMx3Yuuy .icon-shape .label{text-align:center;}#mermaid-svg-QS8jZAJDYMx3Yuuy .node.clickable{cursor:pointer;}#mermaid-svg-QS8jZAJDYMx3Yuuy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QS8jZAJDYMx3Yuuy .arrowheadPath{fill:#333333;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QS8jZAJDYMx3Yuuy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QS8jZAJDYMx3Yuuy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QS8jZAJDYMx3Yuuy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QS8jZAJDYMx3Yuuy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QS8jZAJDYMx3Yuuy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QS8jZAJDYMx3Yuuy .cluster text{fill:#333;}#mermaid-svg-QS8jZAJDYMx3Yuuy .cluster span{color:#333;}#mermaid-svg-QS8jZAJDYMx3Yuuy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QS8jZAJDYMx3Yuuy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QS8jZAJDYMx3Yuuy rect.text{fill:none;stroke-width:0;}#mermaid-svg-QS8jZAJDYMx3Yuuy .icon-shape,#mermaid-svg-QS8jZAJDYMx3Yuuy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QS8jZAJDYMx3Yuuy .icon-shape p,#mermaid-svg-QS8jZAJDYMx3Yuuy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QS8jZAJDYMx3Yuuy .icon-shape .label rect,#mermaid-svg-QS8jZAJDYMx3Yuuy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QS8jZAJDYMx3Yuuy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QS8jZAJDYMx3Yuuy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QS8jZAJDYMx3Yuuy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-QS8jZAJDYMx3Yuuy .service>*{fill:#FFEBEE!important;stroke:#D32F2F!important;color:#333!important;}#mermaid-svg-QS8jZAJDYMx3Yuuy .service span{fill:#FFEBEE!important;stroke:#D32F2F!important;color:#333!important;}#mermaid-svg-QS8jZAJDYMx3Yuuy .service tspan{fill:#333!important;}#mermaid-svg-QS8jZAJDYMx3Yuuy .risk>*{fill:#FFF3E0!important;stroke:#F57C00!important;color:#333!important;}#mermaid-svg-QS8jZAJDYMx3Yuuy .risk span{fill:#FFF3E0!important;stroke:#F57C00!important;color:#333!important;}#mermaid-svg-QS8jZAJDYMx3Yuuy .risk tspan{fill:#333!important;} ❌ 复制到别处忘 unlock
→ 死锁
❌ 业务方法被日志淹没
❌ 想加新功能要改 N 个方法
📦 exportExcel 方法
(15 行代码)
📝 log.info 开始
🔒 redisLock.tryLock
📋 auditLog 记录
💼 业务逻辑
(真正的 2 行)
📝 log.info 完成
⚠️ 死锁风险
⚠️ 业务不清晰
⚠️ 侵入性大
| 痛点 | 实际后果 |
|---|---|
| 🐌 业务被淹没 | 真正干活的代码只剩 2 行,剩下都是"边角料" |
| 💥 易出错 | 复制到其他地方就忘了 unlock → 死锁 |
| 🔗 侵入性大 | 想加"导出限流"功能,要改所有导出方法 |
2.2 用"快递员类比"理解 AOP
想象一下现实生活:
你开了一家快递柜。客户来取件时,快递柜本身只做一件事:取件。
但你想加点增值服务:"客户取件时发条短信提醒"、"客户取件时记录取件时间"......
这些"非取件本身"的功能,不能塞进取件逻辑里------因为塞进去会污染取件代码。
于是你搞了一个"钩子":在客户取件的前后,自动触发短信 / 记录逻辑。
这个"钩子"就是 AOP 切面。
| 角色 | 现实类比 | 代码对应 |
|---|---|---|
| 快递柜 | 业务方法 | exportExcel() |
| 客户取件 | 方法执行 | proceed() |
| 短信提醒功能 | 横切逻辑 | 切面 @Around |
| "客户取件时发短信" | 触发规则 | 切入点 @annotation |
2.3 引入 AOP 后的代码长啥样
java
@DistributedLock(key = "export:lock", expireTime = 30)
@Log("导出用户 Excel")
public void exportExcel(UserQuery query) {
// 干干净净的业务代码
List<User> list = userDao.list(query);
ExcelUtil.write(list);
}
锁 / 日志 / 审计全部由切面自动处理。看着就清爽多了。
2.4 AOP 三大核心价值
| 价值 | 含义 | 现实类比 |
|---|---|---|
| 🎯 解耦 | 把横切关注点从业务代码里抽出去 | 快递柜只管取件,短信单独发 |
| 🛡️ 统一管理 | 所有方法的日志格式 / 锁策略 "一处定义,全局生效" | 短信模板只写一次 |
| 🔧 可插拔 | 加 / 减切面不改业务代码(开关一样) | 哪天不想发短信,关掉即可 |
2.5 AOP 5 个核心概念(用实习生能听懂的话)
#mermaid-svg-WrTzKkcswBsRV8zX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WrTzKkcswBsRV8zX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WrTzKkcswBsRV8zX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WrTzKkcswBsRV8zX .error-icon{fill:#552222;}#mermaid-svg-WrTzKkcswBsRV8zX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WrTzKkcswBsRV8zX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WrTzKkcswBsRV8zX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WrTzKkcswBsRV8zX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WrTzKkcswBsRV8zX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WrTzKkcswBsRV8zX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WrTzKkcswBsRV8zX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WrTzKkcswBsRV8zX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WrTzKkcswBsRV8zX .marker.cross{stroke:#333333;}#mermaid-svg-WrTzKkcswBsRV8zX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WrTzKkcswBsRV8zX p{margin:0;}#mermaid-svg-WrTzKkcswBsRV8zX .edge{stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .section--1 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section--1 path,#mermaid-svg-WrTzKkcswBsRV8zX .section--1 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section--1 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section--1 text{fill:#ffffff;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth--1{stroke-width:17;}#mermaid-svg-WrTzKkcswBsRV8zX .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-0 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-0 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-0 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-0 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-0 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-0{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-0{stroke-width:14;}#mermaid-svg-WrTzKkcswBsRV8zX .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-1 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-1 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-1 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-1 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-1 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-1{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-1{stroke-width:11;}#mermaid-svg-WrTzKkcswBsRV8zX .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-2 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-2 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-2 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-2 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-2 text{fill:#ffffff;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-2{stroke-width:8;}#mermaid-svg-WrTzKkcswBsRV8zX .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-3 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-3 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-3 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-3 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-3 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-3{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-3{stroke-width:5;}#mermaid-svg-WrTzKkcswBsRV8zX .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-4 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-4 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-4 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-4 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-4 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-4{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-4{stroke-width:2;}#mermaid-svg-WrTzKkcswBsRV8zX .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-5 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-5 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-5 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-5 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-5 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-5{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-5{stroke-width:-1;}#mermaid-svg-WrTzKkcswBsRV8zX .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-6 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-6 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-6 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-6 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-6 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-6{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-6{stroke-width:-4;}#mermaid-svg-WrTzKkcswBsRV8zX .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-7 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-7 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-7 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-7 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-7 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-7{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-7{stroke-width:-7;}#mermaid-svg-WrTzKkcswBsRV8zX .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-8 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-8 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-8 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-8 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-8 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-8{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-8{stroke-width:-10;}#mermaid-svg-WrTzKkcswBsRV8zX .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-9 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-9 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-9 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-9 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-9 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-9{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-9{stroke-width:-13;}#mermaid-svg-WrTzKkcswBsRV8zX .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-10 rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-10 path,#mermaid-svg-WrTzKkcswBsRV8zX .section-10 circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-10 polygon,#mermaid-svg-WrTzKkcswBsRV8zX .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-10 text{fill:black;}#mermaid-svg-WrTzKkcswBsRV8zX .node-icon-10{font-size:40px;color:black;}#mermaid-svg-WrTzKkcswBsRV8zX .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .edge-depth-10{stroke-width:-16;}#mermaid-svg-WrTzKkcswBsRV8zX .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled,#mermaid-svg-WrTzKkcswBsRV8zX .disabled circle,#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:lightgray;}#mermaid-svg-WrTzKkcswBsRV8zX .disabled text{fill:#efefef;}#mermaid-svg-WrTzKkcswBsRV8zX .section-root rect,#mermaid-svg-WrTzKkcswBsRV8zX .section-root path,#mermaid-svg-WrTzKkcswBsRV8zX .section-root circle,#mermaid-svg-WrTzKkcswBsRV8zX .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-WrTzKkcswBsRV8zX .section-root text{fill:#ffffff;}#mermaid-svg-WrTzKkcswBsRV8zX .section-root span{color:#ffffff;}#mermaid-svg-WrTzKkcswBsRV8zX .section-2 span{color:#ffffff;}#mermaid-svg-WrTzKkcswBsRV8zX .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-WrTzKkcswBsRV8zX .edge{fill:none;}#mermaid-svg-WrTzKkcswBsRV8zX .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-WrTzKkcswBsRV8zX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} AOP
5 个核心概念
Aspect 切面
要插入的"通用逻辑"
类比:快递柜的"取件提醒短信"功能
Join Point 连接点
业务代码里"能被插入"的位置
类比:快递柜的"放件"和"取件"两个动作
Pointcut 切入点
哪些连接点要插入
类比:哪些快递柜需要装"提醒短信"
Advice 通知
插入的时机
前置 @Before
后置 @After
环绕 @Around
返回后 @AfterReturning
异常后 @AfterThrowing
Target 目标对象
真正干活的业务方法
类比:快递柜本身
| 术语 | 大白话 | 类比 |
|---|---|---|
| Aspect(切面) | 你要插入的"通用逻辑" | 快递柜的"取件提醒短信"功能 |
| Join Point(连接点) | 业务代码里"能被插入"的位置 | 快递柜的"放件"和"取件"两个动作 |
| Pointcut(切入点) | 哪些连接点要插入 | 哪些快递柜需要装"提醒短信" |
| Advice(通知) | 插入的时机(前置/后置/环绕) | "放件时发"还是"取件时发" |
| Target(目标对象) | 真正干活的业务方法 | 快递柜本身 |
💡 一句话串起来 :Pointcut 决定"切哪里",Advice 决定"怎么切",Aspect 是"切面"这个整体。
2.6 Spring AOP vs AspectJ
- Spring AOP :运行时织入,基于代理(动态代理 / CGLIB),只能拦截 Spring 管理的 Bean------99% 的项目用它
- AspectJ:编译期 / 类加载期织入,功能更强但配置复杂
新人建议:先把 Spring AOP 用熟,AspectJ 以后再碰。
三、第一次看懂切面:公司项目里的"代码考古"
3.1 我的"考古"过程
刚拿到那个 @DistributedLock 注解时,我完全懵。后来我用 IDEA 的"ctrl+click 大法",一步步把整个链路扒了出来:
#mermaid-svg-ETlns7ZA2XjFuULP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ETlns7ZA2XjFuULP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ETlns7ZA2XjFuULP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ETlns7ZA2XjFuULP .error-icon{fill:#552222;}#mermaid-svg-ETlns7ZA2XjFuULP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ETlns7ZA2XjFuULP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ETlns7ZA2XjFuULP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ETlns7ZA2XjFuULP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ETlns7ZA2XjFuULP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ETlns7ZA2XjFuULP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ETlns7ZA2XjFuULP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ETlns7ZA2XjFuULP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ETlns7ZA2XjFuULP .marker.cross{stroke:#333333;}#mermaid-svg-ETlns7ZA2XjFuULP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ETlns7ZA2XjFuULP p{margin:0;}#mermaid-svg-ETlns7ZA2XjFuULP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ETlns7ZA2XjFuULP .cluster-label text{fill:#333;}#mermaid-svg-ETlns7ZA2XjFuULP .cluster-label span{color:#333;}#mermaid-svg-ETlns7ZA2XjFuULP .cluster-label span p{background-color:transparent;}#mermaid-svg-ETlns7ZA2XjFuULP .label text,#mermaid-svg-ETlns7ZA2XjFuULP span{fill:#333;color:#333;}#mermaid-svg-ETlns7ZA2XjFuULP .node rect,#mermaid-svg-ETlns7ZA2XjFuULP .node circle,#mermaid-svg-ETlns7ZA2XjFuULP .node ellipse,#mermaid-svg-ETlns7ZA2XjFuULP .node polygon,#mermaid-svg-ETlns7ZA2XjFuULP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ETlns7ZA2XjFuULP .rough-node .label text,#mermaid-svg-ETlns7ZA2XjFuULP .node .label text,#mermaid-svg-ETlns7ZA2XjFuULP .image-shape .label,#mermaid-svg-ETlns7ZA2XjFuULP .icon-shape .label{text-anchor:middle;}#mermaid-svg-ETlns7ZA2XjFuULP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ETlns7ZA2XjFuULP .rough-node .label,#mermaid-svg-ETlns7ZA2XjFuULP .node .label,#mermaid-svg-ETlns7ZA2XjFuULP .image-shape .label,#mermaid-svg-ETlns7ZA2XjFuULP .icon-shape .label{text-align:center;}#mermaid-svg-ETlns7ZA2XjFuULP .node.clickable{cursor:pointer;}#mermaid-svg-ETlns7ZA2XjFuULP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ETlns7ZA2XjFuULP .arrowheadPath{fill:#333333;}#mermaid-svg-ETlns7ZA2XjFuULP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ETlns7ZA2XjFuULP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ETlns7ZA2XjFuULP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ETlns7ZA2XjFuULP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ETlns7ZA2XjFuULP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ETlns7ZA2XjFuULP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ETlns7ZA2XjFuULP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ETlns7ZA2XjFuULP .cluster text{fill:#333;}#mermaid-svg-ETlns7ZA2XjFuULP .cluster span{color:#333;}#mermaid-svg-ETlns7ZA2XjFuULP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ETlns7ZA2XjFuULP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ETlns7ZA2XjFuULP rect.text{fill:none;stroke-width:0;}#mermaid-svg-ETlns7ZA2XjFuULP .icon-shape,#mermaid-svg-ETlns7ZA2XjFuULP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ETlns7ZA2XjFuULP .icon-shape p,#mermaid-svg-ETlns7ZA2XjFuULP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ETlns7ZA2XjFuULP .icon-shape .label rect,#mermaid-svg-ETlns7ZA2XjFuULP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ETlns7ZA2XjFuULP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ETlns7ZA2XjFuULP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ETlns7ZA2XjFuULP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-ETlns7ZA2XjFuULP .step>*{fill:#E8F4FD!important;stroke:#4A90E2!important;color:#333!important;}#mermaid-svg-ETlns7ZA2XjFuULP .step span{fill:#E8F4FD!important;stroke:#4A90E2!important;color:#333!important;}#mermaid-svg-ETlns7ZA2XjFuULP .step tspan{fill:#333!important;}#mermaid-svg-ETlns7ZA2XjFuULP .result>*{fill:#7ED321!important;stroke:#5C9C1A!important;color:#fff!important;}#mermaid-svg-ETlns7ZA2XjFuULP .result span{fill:#7ED321!important;stroke:#5C9C1A!important;color:#fff!important;}#mermaid-svg-ETlns7ZA2XjFuULP .result tspan{fill:#fff!important;} 看到 @DistributedLock 注解
ctrl+click 看注解定义
(几个参数而已)
ctrl+click 找使用位置
(在 exportExcel 方法上)
ctrl+click 找'谁处理这个注解'
(找到切面类)
看 @Around 包裹的代码
(try/finally 加锁解锁)
💡 突然开窍:
注解 + 切面 = 一对
6 步流程总结成一句话:
看到注解 → 看注解定义 → 看哪里用了 → 看谁处理 → 看处理逻辑 → 哦原来是这样!
3.2 一个注解同时支持"互斥锁"和"信号量限流"
这是我实习项目里最巧妙的设计 ------@DistributedLock 注解有一个参数 maxConcurrent:
#mermaid-svg-XLhRoHKbxy4gMk9w{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XLhRoHKbxy4gMk9w .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XLhRoHKbxy4gMk9w .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XLhRoHKbxy4gMk9w .error-icon{fill:#552222;}#mermaid-svg-XLhRoHKbxy4gMk9w .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XLhRoHKbxy4gMk9w .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XLhRoHKbxy4gMk9w .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XLhRoHKbxy4gMk9w .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XLhRoHKbxy4gMk9w .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XLhRoHKbxy4gMk9w .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XLhRoHKbxy4gMk9w .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XLhRoHKbxy4gMk9w .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XLhRoHKbxy4gMk9w .marker.cross{stroke:#333333;}#mermaid-svg-XLhRoHKbxy4gMk9w svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XLhRoHKbxy4gMk9w p{margin:0;}#mermaid-svg-XLhRoHKbxy4gMk9w .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XLhRoHKbxy4gMk9w .cluster-label text{fill:#333;}#mermaid-svg-XLhRoHKbxy4gMk9w .cluster-label span{color:#333;}#mermaid-svg-XLhRoHKbxy4gMk9w .cluster-label span p{background-color:transparent;}#mermaid-svg-XLhRoHKbxy4gMk9w .label text,#mermaid-svg-XLhRoHKbxy4gMk9w span{fill:#333;color:#333;}#mermaid-svg-XLhRoHKbxy4gMk9w .node rect,#mermaid-svg-XLhRoHKbxy4gMk9w .node circle,#mermaid-svg-XLhRoHKbxy4gMk9w .node ellipse,#mermaid-svg-XLhRoHKbxy4gMk9w .node polygon,#mermaid-svg-XLhRoHKbxy4gMk9w .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XLhRoHKbxy4gMk9w .rough-node .label text,#mermaid-svg-XLhRoHKbxy4gMk9w .node .label text,#mermaid-svg-XLhRoHKbxy4gMk9w .image-shape .label,#mermaid-svg-XLhRoHKbxy4gMk9w .icon-shape .label{text-anchor:middle;}#mermaid-svg-XLhRoHKbxy4gMk9w .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XLhRoHKbxy4gMk9w .rough-node .label,#mermaid-svg-XLhRoHKbxy4gMk9w .node .label,#mermaid-svg-XLhRoHKbxy4gMk9w .image-shape .label,#mermaid-svg-XLhRoHKbxy4gMk9w .icon-shape .label{text-align:center;}#mermaid-svg-XLhRoHKbxy4gMk9w .node.clickable{cursor:pointer;}#mermaid-svg-XLhRoHKbxy4gMk9w .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XLhRoHKbxy4gMk9w .arrowheadPath{fill:#333333;}#mermaid-svg-XLhRoHKbxy4gMk9w .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XLhRoHKbxy4gMk9w .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XLhRoHKbxy4gMk9w .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XLhRoHKbxy4gMk9w .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XLhRoHKbxy4gMk9w .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XLhRoHKbxy4gMk9w .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XLhRoHKbxy4gMk9w .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XLhRoHKbxy4gMk9w .cluster text{fill:#333;}#mermaid-svg-XLhRoHKbxy4gMk9w .cluster span{color:#333;}#mermaid-svg-XLhRoHKbxy4gMk9w div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XLhRoHKbxy4gMk9w .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XLhRoHKbxy4gMk9w rect.text{fill:none;stroke-width:0;}#mermaid-svg-XLhRoHKbxy4gMk9w .icon-shape,#mermaid-svg-XLhRoHKbxy4gMk9w .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XLhRoHKbxy4gMk9w .icon-shape p,#mermaid-svg-XLhRoHKbxy4gMk9w .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XLhRoHKbxy4gMk9w .icon-shape .label rect,#mermaid-svg-XLhRoHKbxy4gMk9w .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XLhRoHKbxy4gMk9w .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XLhRoHKbxy4gMk9w .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XLhRoHKbxy4gMk9w :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-XLhRoHKbxy4gMk9w .ann>*{fill:#9013FE!important;stroke:#5C00B3!important;color:#fff!important;}#mermaid-svg-XLhRoHKbxy4gMk9w .ann span{fill:#9013FE!important;stroke:#5C00B3!important;color:#fff!important;}#mermaid-svg-XLhRoHKbxy4gMk9w .ann tspan{fill:#fff!important;}#mermaid-svg-XLhRoHKbxy4gMk9w .mutex>*{fill:#FF6B6B!important;stroke:#C92A2A!important;color:#fff!important;}#mermaid-svg-XLhRoHKbxy4gMk9w .mutex span{fill:#FF6B6B!important;stroke:#C92A2A!important;color:#fff!important;}#mermaid-svg-XLhRoHKbxy4gMk9w .mutex tspan{fill:#fff!important;}#mermaid-svg-XLhRoHKbxy4gMk9w .sem>*{fill:#4A90E2!important;stroke:#2E5C8A!important;color:#fff!important;}#mermaid-svg-XLhRoHKbxy4gMk9w .sem span{fill:#4A90E2!important;stroke:#2E5C8A!important;color:#fff!important;}#mermaid-svg-XLhRoHKbxy4gMk9w .sem tspan{fill:#fff!important;} 1
>1
@DistributedLock
maxConcurrent = ?
= 1
互斥锁
= 10
信号量限流
maxConcurrent = 1→ 互斥锁(同一时刻只能一个请求拿到锁)maxConcurrent = 10→ 信号量限流(最多 10 个请求同时通过)
一个注解搞定两类并发控制。这就是"把业务语义编码到注解参数里"的优秀实践。
3.3 为什么项目里没有"日志切面 / 权限切面"?
我一开始以为公司肯定有各种高大上的切面(操作日志、权限校验、参数校验...),结果发现:
| 功能 | 实际方案 |
|---|---|
| 操作日志 | Logback + MDC + 拦截器 |
| 权限校验 | Spring Security + 自定义拦截器 |
| 参数校验 | JSR-303 + @Valid |
| 异常处理 | @ControllerAdvice + @ExceptionHandler |
这给我一个很重要的认知:
AOP 不是"什么都能切",而是"只解决业务代码不该重复写的痛点"。 适合切面的是那些"出现在 N 个方法里、但又跟业务无关"的代码(锁、日志、审计、计时)。
四、动手时间:从 0 写一个 @Log 切面
这一节是重头戏。我手把手带你写第一个 AOP 切面,照着做就能跑通。
4.1 写一个 @Log 切面的 5 步流程
#mermaid-svg-rH093GXxptwSg9iu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rH093GXxptwSg9iu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rH093GXxptwSg9iu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rH093GXxptwSg9iu .error-icon{fill:#552222;}#mermaid-svg-rH093GXxptwSg9iu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rH093GXxptwSg9iu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rH093GXxptwSg9iu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rH093GXxptwSg9iu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rH093GXxptwSg9iu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rH093GXxptwSg9iu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rH093GXxptwSg9iu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rH093GXxptwSg9iu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rH093GXxptwSg9iu .marker.cross{stroke:#333333;}#mermaid-svg-rH093GXxptwSg9iu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rH093GXxptwSg9iu p{margin:0;}#mermaid-svg-rH093GXxptwSg9iu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rH093GXxptwSg9iu .cluster-label text{fill:#333;}#mermaid-svg-rH093GXxptwSg9iu .cluster-label span{color:#333;}#mermaid-svg-rH093GXxptwSg9iu .cluster-label span p{background-color:transparent;}#mermaid-svg-rH093GXxptwSg9iu .label text,#mermaid-svg-rH093GXxptwSg9iu span{fill:#333;color:#333;}#mermaid-svg-rH093GXxptwSg9iu .node rect,#mermaid-svg-rH093GXxptwSg9iu .node circle,#mermaid-svg-rH093GXxptwSg9iu .node ellipse,#mermaid-svg-rH093GXxptwSg9iu .node polygon,#mermaid-svg-rH093GXxptwSg9iu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rH093GXxptwSg9iu .rough-node .label text,#mermaid-svg-rH093GXxptwSg9iu .node .label text,#mermaid-svg-rH093GXxptwSg9iu .image-shape .label,#mermaid-svg-rH093GXxptwSg9iu .icon-shape .label{text-anchor:middle;}#mermaid-svg-rH093GXxptwSg9iu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rH093GXxptwSg9iu .rough-node .label,#mermaid-svg-rH093GXxptwSg9iu .node .label,#mermaid-svg-rH093GXxptwSg9iu .image-shape .label,#mermaid-svg-rH093GXxptwSg9iu .icon-shape .label{text-align:center;}#mermaid-svg-rH093GXxptwSg9iu .node.clickable{cursor:pointer;}#mermaid-svg-rH093GXxptwSg9iu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rH093GXxptwSg9iu .arrowheadPath{fill:#333333;}#mermaid-svg-rH093GXxptwSg9iu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rH093GXxptwSg9iu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rH093GXxptwSg9iu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rH093GXxptwSg9iu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rH093GXxptwSg9iu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rH093GXxptwSg9iu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rH093GXxptwSg9iu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rH093GXxptwSg9iu .cluster text{fill:#333;}#mermaid-svg-rH093GXxptwSg9iu .cluster span{color:#333;}#mermaid-svg-rH093GXxptwSg9iu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rH093GXxptwSg9iu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rH093GXxptwSg9iu rect.text{fill:none;stroke-width:0;}#mermaid-svg-rH093GXxptwSg9iu .icon-shape,#mermaid-svg-rH093GXxptwSg9iu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rH093GXxptwSg9iu .icon-shape p,#mermaid-svg-rH093GXxptwSg9iu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rH093GXxptwSg9iu .icon-shape .label rect,#mermaid-svg-rH093GXxptwSg9iu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rH093GXxptwSg9iu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rH093GXxptwSg9iu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rH093GXxptwSg9iu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-rH093GXxptwSg9iu .step>*{fill:#FFF7E6!important;stroke:#F5A623!important;color:#333!important;}#mermaid-svg-rH093GXxptwSg9iu .step span{fill:#FFF7E6!important;stroke:#F5A623!important;color:#333!important;}#mermaid-svg-rH093GXxptwSg9iu .step tspan{fill:#333!important;}#mermaid-svg-rH093GXxptwSg9iu .result>*{fill:#7ED321!important;stroke:#5C9C1A!important;color:#fff!important;}#mermaid-svg-rH093GXxptwSg9iu .result span{fill:#7ED321!important;stroke:#5C9C1A!important;color:#fff!important;}#mermaid-svg-rH093GXxptwSg9iu .result tspan{fill:#fff!important;} 第 1 步
📦 引入 spring-boot-starter-aop
第 2 步
📝 定义 @Log 注解
第 3 步
⚙️ 写 @Aspect 切面类
第 4 步
🚀 业务方法上加注解测试
✅ 第一个 AOP 切面跑通!
4.2 第 1 步:引入依赖
xml
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.3 第 2 步:定义 @Log 注解
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
String value() default ""; // 日志描述
boolean printParams() default true; // 是否打印入参
boolean printResult() default false; // 是否打印返回值
}
4.4 第 3 步:写切面类
java
@Aspect
@Component
@Slf4j
public class LogAspect {
// 切入点:拦截所有带 @Log 注解的方法
@Pointcut("@annotation(com.example.demo.annotation.Log)")
public void logPointcut() {}
// 环绕通知:方法执行前后都做点什么
@Around("logPointcut() && @annotation(log)")
public Object around(ProceedingJoinPoint pjp, Log log) throws Throwable {
long start = System.currentTimeMillis();
String methodName = pjp.getSignature().getName();
// 1. 打印入参
if (log.printParams()) {
log.info("[{}] 开始执行,入参:{}", methodName, pjp.getArgs());
}
Object result = null;
try {
// 2. 执行目标方法
result = pjp.proceed();
} catch (Throwable e) {
// 3. 异常处理(重要:必须重抛!)
log.error("[{}] 执行异常:{}", methodName, e.getMessage(), e);
throw e; // ⚠️ 必须重抛,否则业务方不知道出错
} finally {
// 4. 打印耗时
long cost = System.currentTimeMillis() - start;
if (log.printResult()) {
log.info("[{}] 执行完成,耗时:{}ms,返回:{}", methodName, cost, result);
} else {
log.info("[{}] 执行完成,耗时:{}ms", methodName, cost);
}
}
return result;
}
}
三个关键点
| 关键点 | 说明 |
|---|---|
@Aspect + @Component |
告诉 Spring 这是一个切面类,别忘了加 @Component 否则 Spring 不扫描 |
@Pointcut + @annotation |
最常用的切入点表达式,精确拦截 |
@Around + pjp.proceed() |
环绕通知能控制方法是否执行、何时执行 |
4.5 第 4 步:业务方法上加注解
java
@Service
public class UserService {
@Log(value = "查询用户列表", printParams = true, printResult = true)
public List<User> listUsers(UserQuery query) {
// 干干净净的业务代码
return userDao.list(query);
}
}
调用 listUsers 后控制台输出:
[listUsers] 开始执行,入参:[UserQuery(name=, age=0)]
[listUsers] 执行完成,耗时:23ms,返回:[User(id=1, name=张三), ...]
第一个 AOP 切面跑通了! 🎉
4.6 4 种通知的执行顺序图
@AfterReturning (返回后) @After (后置) @Around (后段) 目标方法 @Around (前段) @Before (前置) 业务调用方 @AfterReturning (返回后) @After (后置) @Around (后段) 目标方法 @Around (前段) @Before (前置) 业务调用方 #mermaid-svg-LyHP4OUnieiQhPZu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LyHP4OUnieiQhPZu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LyHP4OUnieiQhPZu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LyHP4OUnieiQhPZu .error-icon{fill:#552222;}#mermaid-svg-LyHP4OUnieiQhPZu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LyHP4OUnieiQhPZu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LyHP4OUnieiQhPZu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LyHP4OUnieiQhPZu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LyHP4OUnieiQhPZu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LyHP4OUnieiQhPZu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LyHP4OUnieiQhPZu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LyHP4OUnieiQhPZu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LyHP4OUnieiQhPZu .marker.cross{stroke:#333333;}#mermaid-svg-LyHP4OUnieiQhPZu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LyHP4OUnieiQhPZu p{margin:0;}#mermaid-svg-LyHP4OUnieiQhPZu .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LyHP4OUnieiQhPZu text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-LyHP4OUnieiQhPZu .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LyHP4OUnieiQhPZu .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-LyHP4OUnieiQhPZu .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-LyHP4OUnieiQhPZu .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-LyHP4OUnieiQhPZu #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-LyHP4OUnieiQhPZu .sequenceNumber{fill:white;}#mermaid-svg-LyHP4OUnieiQhPZu #sequencenumber{fill:#333;}#mermaid-svg-LyHP4OUnieiQhPZu #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-LyHP4OUnieiQhPZu .messageText{fill:#333;stroke:none;}#mermaid-svg-LyHP4OUnieiQhPZu .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LyHP4OUnieiQhPZu .labelText,#mermaid-svg-LyHP4OUnieiQhPZu .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-LyHP4OUnieiQhPZu .loopText,#mermaid-svg-LyHP4OUnieiQhPZu .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-LyHP4OUnieiQhPZu .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LyHP4OUnieiQhPZu .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-LyHP4OUnieiQhPZu .noteText,#mermaid-svg-LyHP4OUnieiQhPZu .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-LyHP4OUnieiQhPZu .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LyHP4OUnieiQhPZu .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LyHP4OUnieiQhPZu .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LyHP4OUnieiQhPZu .actorPopupMenu{position:absolute;}#mermaid-svg-LyHP4OUnieiQhPZu .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-LyHP4OUnieiQhPZu .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LyHP4OUnieiQhPZu .actor-man circle,#mermaid-svg-LyHP4OUnieiQhPZu line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-LyHP4OUnieiQhPZu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ⚠️ 正常流程 @AfterThrowing 不触发 1. 进入前置 1 2. 进入环绕前段 2 3. proceed() 3 4. 返回结果 4 5. 环绕后段 5 6. 返回后 6 7. 最终结果 7
💡 记忆口诀 :前 → 环前 → 目标 → 环后 → 后 → 返回 (异常时
AfterThrowing替代AfterReturning)
4.7 几种常见切入点表达式
| 表达式 | 含义 | 例子 |
|---|---|---|
@annotation(com.example.Log) |
拦截带 @Log 注解的方法 |
最常用 ⭐⭐⭐ |
execution(* com.example.service..*(..)) |
拦截 service 包下所有方法 | 太粗粒度慎用 |
within(com.example.service..*) |
拦截 service 包下所有类的所有方法 | 同上 |
@within(com.example.annotation.Log) |
拦截带 @Log 注解的类下的所有方法 |
类级别拦截 |
🎯 经验 :项目里只允许用
@annotation这种细粒度形式 ,粗粒度切入点(execution/within)会让你追 bug 追到哭。
五、踩坑预警:自己写切面时最容易犯的 5 个错
坑 1:同类内部方法调用,AOP 不生效
java
@Service
public class UserService {
@Log("查询用户")
public List<User> listUsers() {
return doQuery(); // ⚠️ AOP 失效!
}
@Log("实际查询")
private List<User> doQuery() {
return userDao.list();
}
}
原因 :this.doQuery() 走的是原始对象,不是 Spring 代理对象。
#mermaid-svg-5usiqt1rSfts8kWI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5usiqt1rSfts8kWI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5usiqt1rSfts8kWI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5usiqt1rSfts8kWI .error-icon{fill:#552222;}#mermaid-svg-5usiqt1rSfts8kWI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5usiqt1rSfts8kWI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5usiqt1rSfts8kWI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5usiqt1rSfts8kWI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5usiqt1rSfts8kWI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5usiqt1rSfts8kWI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5usiqt1rSfts8kWI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5usiqt1rSfts8kWI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5usiqt1rSfts8kWI .marker.cross{stroke:#333333;}#mermaid-svg-5usiqt1rSfts8kWI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5usiqt1rSfts8kWI p{margin:0;}#mermaid-svg-5usiqt1rSfts8kWI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5usiqt1rSfts8kWI .cluster-label text{fill:#333;}#mermaid-svg-5usiqt1rSfts8kWI .cluster-label span{color:#333;}#mermaid-svg-5usiqt1rSfts8kWI .cluster-label span p{background-color:transparent;}#mermaid-svg-5usiqt1rSfts8kWI .label text,#mermaid-svg-5usiqt1rSfts8kWI span{fill:#333;color:#333;}#mermaid-svg-5usiqt1rSfts8kWI .node rect,#mermaid-svg-5usiqt1rSfts8kWI .node circle,#mermaid-svg-5usiqt1rSfts8kWI .node ellipse,#mermaid-svg-5usiqt1rSfts8kWI .node polygon,#mermaid-svg-5usiqt1rSfts8kWI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5usiqt1rSfts8kWI .rough-node .label text,#mermaid-svg-5usiqt1rSfts8kWI .node .label text,#mermaid-svg-5usiqt1rSfts8kWI .image-shape .label,#mermaid-svg-5usiqt1rSfts8kWI .icon-shape .label{text-anchor:middle;}#mermaid-svg-5usiqt1rSfts8kWI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5usiqt1rSfts8kWI .rough-node .label,#mermaid-svg-5usiqt1rSfts8kWI .node .label,#mermaid-svg-5usiqt1rSfts8kWI .image-shape .label,#mermaid-svg-5usiqt1rSfts8kWI .icon-shape .label{text-align:center;}#mermaid-svg-5usiqt1rSfts8kWI .node.clickable{cursor:pointer;}#mermaid-svg-5usiqt1rSfts8kWI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5usiqt1rSfts8kWI .arrowheadPath{fill:#333333;}#mermaid-svg-5usiqt1rSfts8kWI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5usiqt1rSfts8kWI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5usiqt1rSfts8kWI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5usiqt1rSfts8kWI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5usiqt1rSfts8kWI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5usiqt1rSfts8kWI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5usiqt1rSfts8kWI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5usiqt1rSfts8kWI .cluster text{fill:#333;}#mermaid-svg-5usiqt1rSfts8kWI .cluster span{color:#333;}#mermaid-svg-5usiqt1rSfts8kWI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5usiqt1rSfts8kWI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5usiqt1rSfts8kWI rect.text{fill:none;stroke-width:0;}#mermaid-svg-5usiqt1rSfts8kWI .icon-shape,#mermaid-svg-5usiqt1rSfts8kWI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5usiqt1rSfts8kWI .icon-shape p,#mermaid-svg-5usiqt1rSfts8kWI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5usiqt1rSfts8kWI .icon-shape .label rect,#mermaid-svg-5usiqt1rSfts8kWI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5usiqt1rSfts8kWI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5usiqt1rSfts8kWI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5usiqt1rSfts8kWI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-5usiqt1rSfts8kWI .ok>*{fill:#E8F5E9!important;stroke:#388E3C!important;color:#333!important;}#mermaid-svg-5usiqt1rSfts8kWI .ok span{fill:#E8F5E9!important;stroke:#388E3C!important;color:#333!important;}#mermaid-svg-5usiqt1rSfts8kWI .ok tspan{fill:#333!important;}#mermaid-svg-5usiqt1rSfts8kWI .err>*{fill:#FFEBEE!important;stroke:#D32F2F!important;color:#fff!important;}#mermaid-svg-5usiqt1rSfts8kWI .err span{fill:#FFEBEE!important;stroke:#D32F2F!important;color:#fff!important;}#mermaid-svg-5usiqt1rSfts8kWI .err tspan{fill:#fff!important;} this.doQuery()
外部调用 listUsers()
Spring 代理对象
(AOP 生效)
this.doQuery()
原始对象
(AOP 不生效)
❌ @Log 注解被绕过
解决方法
-
方法 1 :注入自己
java@Autowired @Lazy private UserService self; // 注入自己 -
方法 2 :用
AopContext.currentProxy()java((UserService) AopContext.currentProxy()).doQuery();需要在启动类加
@EnableAspectJAutoProxy(exposeProxy = true) -
方法 3 :拆分到不同 Service(最推荐)
坑 2:private / final 方法不能被代理
java
@Service
public class UserService {
@Log // ⚠️ private 方法,代理不到
private void internalMethod() {
// ...
}
@Log // ⚠️ final 方法,代理不到
public final void finalMethod() {
// ...
}
}
原因:
- JDK 动态代理基于接口,不能代理 private / final 方法
- CGLIB 基于继承,final 方法不能被重写
坑 3:异常被 try/catch 吞掉后,@AfterThrowing 不触发
java
@Around("logPointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
return pjp.proceed();
} catch (Exception e) {
log.error("出错了", e);
return null; // ⚠️ 异常被吞!
}
}
坑点:
- 业务方法抛了异常,但被切面吞了
- 业务调用方不知道出错 (得到一个
null) @AfterThrowing也不触发
修复
java
} catch (Exception e) {
log.error("出错了", e);
throw e; // ✅ 必须重抛!
}
坑 4:@Order 写反导致数据源切换 / 事务失效
项目里数据源切换切面用 @Order(1):
#mermaid-svg-PZmQdm9eqrxxceBE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PZmQdm9eqrxxceBE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PZmQdm9eqrxxceBE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PZmQdm9eqrxxceBE .error-icon{fill:#552222;}#mermaid-svg-PZmQdm9eqrxxceBE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PZmQdm9eqrxxceBE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PZmQdm9eqrxxceBE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PZmQdm9eqrxxceBE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PZmQdm9eqrxxceBE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PZmQdm9eqrxxceBE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PZmQdm9eqrxxceBE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PZmQdm9eqrxxceBE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PZmQdm9eqrxxceBE .marker.cross{stroke:#333333;}#mermaid-svg-PZmQdm9eqrxxceBE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PZmQdm9eqrxxceBE p{margin:0;}#mermaid-svg-PZmQdm9eqrxxceBE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PZmQdm9eqrxxceBE .cluster-label text{fill:#333;}#mermaid-svg-PZmQdm9eqrxxceBE .cluster-label span{color:#333;}#mermaid-svg-PZmQdm9eqrxxceBE .cluster-label span p{background-color:transparent;}#mermaid-svg-PZmQdm9eqrxxceBE .label text,#mermaid-svg-PZmQdm9eqrxxceBE span{fill:#333;color:#333;}#mermaid-svg-PZmQdm9eqrxxceBE .node rect,#mermaid-svg-PZmQdm9eqrxxceBE .node circle,#mermaid-svg-PZmQdm9eqrxxceBE .node ellipse,#mermaid-svg-PZmQdm9eqrxxceBE .node polygon,#mermaid-svg-PZmQdm9eqrxxceBE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PZmQdm9eqrxxceBE .rough-node .label text,#mermaid-svg-PZmQdm9eqrxxceBE .node .label text,#mermaid-svg-PZmQdm9eqrxxceBE .image-shape .label,#mermaid-svg-PZmQdm9eqrxxceBE .icon-shape .label{text-anchor:middle;}#mermaid-svg-PZmQdm9eqrxxceBE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PZmQdm9eqrxxceBE .rough-node .label,#mermaid-svg-PZmQdm9eqrxxceBE .node .label,#mermaid-svg-PZmQdm9eqrxxceBE .image-shape .label,#mermaid-svg-PZmQdm9eqrxxceBE .icon-shape .label{text-align:center;}#mermaid-svg-PZmQdm9eqrxxceBE .node.clickable{cursor:pointer;}#mermaid-svg-PZmQdm9eqrxxceBE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PZmQdm9eqrxxceBE .arrowheadPath{fill:#333333;}#mermaid-svg-PZmQdm9eqrxxceBE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PZmQdm9eqrxxceBE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PZmQdm9eqrxxceBE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PZmQdm9eqrxxceBE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PZmQdm9eqrxxceBE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PZmQdm9eqrxxceBE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PZmQdm9eqrxxceBE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PZmQdm9eqrxxceBE .cluster text{fill:#333;}#mermaid-svg-PZmQdm9eqrxxceBE .cluster span{color:#333;}#mermaid-svg-PZmQdm9eqrxxceBE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PZmQdm9eqrxxceBE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PZmQdm9eqrxxceBE rect.text{fill:none;stroke-width:0;}#mermaid-svg-PZmQdm9eqrxxceBE .icon-shape,#mermaid-svg-PZmQdm9eqrxxceBE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PZmQdm9eqrxxceBE .icon-shape p,#mermaid-svg-PZmQdm9eqrxxceBE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PZmQdm9eqrxxceBE .icon-shape .label rect,#mermaid-svg-PZmQdm9eqrxxceBE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PZmQdm9eqrxxceBE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PZmQdm9eqrxxceBE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PZmQdm9eqrxxceBE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-PZmQdm9eqrxxceBE .ok>*{fill:#E8F5E9!important;stroke:#388E3C!important;color:#333!important;}#mermaid-svg-PZmQdm9eqrxxceBE .ok span{fill:#E8F5E9!important;stroke:#388E3C!important;color:#333!important;}#mermaid-svg-PZmQdm9eqrxxceBE .ok tspan{fill:#333!important;} 请求进入
@Order(1) 数据源切面
(最外层)
@Transactional 事务切面
业务方法
提交/回滚事务
清理数据源 ThreadLocal
为什么是 @Order(1)? Spring 的 @Order 语义是"值越小越外层"。
正确顺序 vs 错误顺序
| 顺序 | 效果 |
|---|---|
| ✅ 数据源切面在最外层 | 设置数据源 → 事务切面用这个数据源开启事务 → 业务执行 → 事务提交 → 清理数据源 |
| ❌ 事务切面在最外层 | 事务先开启(绑定主库)→ 数据源切面切到从库(但事务不切换)→ 出现"主库事务内查从库"的诡异现象 |
🎯 经验 :数据源切面、租户切面 这类"需要事务感知"的切面,必须 用
@Order(Ordered.HIGHEST_PRECEDENCE)(值最小,最外层)。
坑 5:返回 null 把业务结果吞了
java
@Around("logPointcut()")
public Object around(ProceedingJoinPoint pjp) {
try {
return pjp.proceed();
} catch (Throwable e) {
log.error("出错了", e);
return null; // ⚠️ 业务结果被吞
}
}
坑点 :@Around 必须返回 proceed() 的结果,否则业务返回值丢失。
修复
java
} catch (Throwable e) {
log.error("出错了", e);
throw e; // ✅ 重抛异常,不要吞
}
return result; // ✅ finally 之外返回正常结果
六、避坑指南:AOP 使用的 5 条军规
#mermaid-svg-OioJ8ieL6NAjK81g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OioJ8ieL6NAjK81g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OioJ8ieL6NAjK81g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OioJ8ieL6NAjK81g .error-icon{fill:#552222;}#mermaid-svg-OioJ8ieL6NAjK81g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OioJ8ieL6NAjK81g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OioJ8ieL6NAjK81g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OioJ8ieL6NAjK81g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OioJ8ieL6NAjK81g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OioJ8ieL6NAjK81g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OioJ8ieL6NAjK81g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OioJ8ieL6NAjK81g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OioJ8ieL6NAjK81g .marker.cross{stroke:#333333;}#mermaid-svg-OioJ8ieL6NAjK81g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OioJ8ieL6NAjK81g p{margin:0;}#mermaid-svg-OioJ8ieL6NAjK81g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OioJ8ieL6NAjK81g .cluster-label text{fill:#333;}#mermaid-svg-OioJ8ieL6NAjK81g .cluster-label span{color:#333;}#mermaid-svg-OioJ8ieL6NAjK81g .cluster-label span p{background-color:transparent;}#mermaid-svg-OioJ8ieL6NAjK81g .label text,#mermaid-svg-OioJ8ieL6NAjK81g span{fill:#333;color:#333;}#mermaid-svg-OioJ8ieL6NAjK81g .node rect,#mermaid-svg-OioJ8ieL6NAjK81g .node circle,#mermaid-svg-OioJ8ieL6NAjK81g .node ellipse,#mermaid-svg-OioJ8ieL6NAjK81g .node polygon,#mermaid-svg-OioJ8ieL6NAjK81g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OioJ8ieL6NAjK81g .rough-node .label text,#mermaid-svg-OioJ8ieL6NAjK81g .node .label text,#mermaid-svg-OioJ8ieL6NAjK81g .image-shape .label,#mermaid-svg-OioJ8ieL6NAjK81g .icon-shape .label{text-anchor:middle;}#mermaid-svg-OioJ8ieL6NAjK81g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OioJ8ieL6NAjK81g .rough-node .label,#mermaid-svg-OioJ8ieL6NAjK81g .node .label,#mermaid-svg-OioJ8ieL6NAjK81g .image-shape .label,#mermaid-svg-OioJ8ieL6NAjK81g .icon-shape .label{text-align:center;}#mermaid-svg-OioJ8ieL6NAjK81g .node.clickable{cursor:pointer;}#mermaid-svg-OioJ8ieL6NAjK81g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OioJ8ieL6NAjK81g .arrowheadPath{fill:#333333;}#mermaid-svg-OioJ8ieL6NAjK81g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OioJ8ieL6NAjK81g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OioJ8ieL6NAjK81g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OioJ8ieL6NAjK81g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OioJ8ieL6NAjK81g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OioJ8ieL6NAjK81g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OioJ8ieL6NAjK81g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OioJ8ieL6NAjK81g .cluster text{fill:#333;}#mermaid-svg-OioJ8ieL6NAjK81g .cluster span{color:#333;}#mermaid-svg-OioJ8ieL6NAjK81g div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OioJ8ieL6NAjK81g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OioJ8ieL6NAjK81g rect.text{fill:none;stroke-width:0;}#mermaid-svg-OioJ8ieL6NAjK81g .icon-shape,#mermaid-svg-OioJ8ieL6NAjK81g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OioJ8ieL6NAjK81g .icon-shape p,#mermaid-svg-OioJ8ieL6NAjK81g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OioJ8ieL6NAjK81g .icon-shape .label rect,#mermaid-svg-OioJ8ieL6NAjK81g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OioJ8ieL6NAjK81g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OioJ8ieL6NAjK81g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OioJ8ieL6NAjK81g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-OioJ8ieL6NAjK81g .rule>*{fill:#F3E5F5!important;stroke:#9013FE!important;color:#333!important;}#mermaid-svg-OioJ8ieL6NAjK81g .rule span{fill:#F3E5F5!important;stroke:#9013FE!important;color:#333!important;}#mermaid-svg-OioJ8ieL6NAjK81g .rule tspan{fill:#333!important;} ① 不滥用
只切'真痛点'
② 注解参数化
一个注解支持多场景
③ 顺序明确
用 @Order 标清楚
④ 不要吞异常
@Around 要重抛
⑤ 注意同类调用
用 self 注入或 AopContext
具体展开:
| 军规 | 解释 | 反例 |
|---|---|---|
| ① 不滥用 | 只切"真痛点",别把"什么都能切"当 KPI | 不要给每个 Service 方法都加 @Log |
| ② 注解参数化 | 一个注解支持多场景 | @DistributedLock 同时支持互斥锁和信号量 |
| ③ 顺序明确 | 用 @Order 标清楚 |
多切面嵌套时 @Order 必须显式标注 |
| ④ 不要吞异常 | @Around 要重抛 |
吞掉异常 = 业务方不知道出错 |
| ⑤ 注意同类调用 | 用 self 注入或 AopContext | this.xxx() 会绕过代理 |
七、如果重来一次,我会怎么学 AOP?
#mermaid-svg-UPhVfHXv1Fk6lQkf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UPhVfHXv1Fk6lQkf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UPhVfHXv1Fk6lQkf .error-icon{fill:#552222;}#mermaid-svg-UPhVfHXv1Fk6lQkf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UPhVfHXv1Fk6lQkf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UPhVfHXv1Fk6lQkf .marker.cross{stroke:#333333;}#mermaid-svg-UPhVfHXv1Fk6lQkf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UPhVfHXv1Fk6lQkf p{margin:0;}#mermaid-svg-UPhVfHXv1Fk6lQkf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UPhVfHXv1Fk6lQkf .cluster-label text{fill:#333;}#mermaid-svg-UPhVfHXv1Fk6lQkf .cluster-label span{color:#333;}#mermaid-svg-UPhVfHXv1Fk6lQkf .cluster-label span p{background-color:transparent;}#mermaid-svg-UPhVfHXv1Fk6lQkf .label text,#mermaid-svg-UPhVfHXv1Fk6lQkf span{fill:#333;color:#333;}#mermaid-svg-UPhVfHXv1Fk6lQkf .node rect,#mermaid-svg-UPhVfHXv1Fk6lQkf .node circle,#mermaid-svg-UPhVfHXv1Fk6lQkf .node ellipse,#mermaid-svg-UPhVfHXv1Fk6lQkf .node polygon,#mermaid-svg-UPhVfHXv1Fk6lQkf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UPhVfHXv1Fk6lQkf .rough-node .label text,#mermaid-svg-UPhVfHXv1Fk6lQkf .node .label text,#mermaid-svg-UPhVfHXv1Fk6lQkf .image-shape .label,#mermaid-svg-UPhVfHXv1Fk6lQkf .icon-shape .label{text-anchor:middle;}#mermaid-svg-UPhVfHXv1Fk6lQkf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UPhVfHXv1Fk6lQkf .rough-node .label,#mermaid-svg-UPhVfHXv1Fk6lQkf .node .label,#mermaid-svg-UPhVfHXv1Fk6lQkf .image-shape .label,#mermaid-svg-UPhVfHXv1Fk6lQkf .icon-shape .label{text-align:center;}#mermaid-svg-UPhVfHXv1Fk6lQkf .node.clickable{cursor:pointer;}#mermaid-svg-UPhVfHXv1Fk6lQkf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UPhVfHXv1Fk6lQkf .arrowheadPath{fill:#333333;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UPhVfHXv1Fk6lQkf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UPhVfHXv1Fk6lQkf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UPhVfHXv1Fk6lQkf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UPhVfHXv1Fk6lQkf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UPhVfHXv1Fk6lQkf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UPhVfHXv1Fk6lQkf .cluster text{fill:#333;}#mermaid-svg-UPhVfHXv1Fk6lQkf .cluster span{color:#333;}#mermaid-svg-UPhVfHXv1Fk6lQkf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UPhVfHXv1Fk6lQkf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UPhVfHXv1Fk6lQkf rect.text{fill:none;stroke-width:0;}#mermaid-svg-UPhVfHXv1Fk6lQkf .icon-shape,#mermaid-svg-UPhVfHXv1Fk6lQkf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UPhVfHXv1Fk6lQkf .icon-shape p,#mermaid-svg-UPhVfHXv1Fk6lQkf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UPhVfHXv1Fk6lQkf .icon-shape .label rect,#mermaid-svg-UPhVfHXv1Fk6lQkf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UPhVfHXv1Fk6lQkf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UPhVfHXv1Fk6lQkf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UPhVfHXv1Fk6lQkf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-UPhVfHXv1Fk6lQkf .step>*{fill:#F3E5F5!important;stroke:#9013FE!important;color:#333!important;}#mermaid-svg-UPhVfHXv1Fk6lQkf .step span{fill:#F3E5F5!important;stroke:#9013FE!important;color:#333!important;}#mermaid-svg-UPhVfHXv1Fk6lQkf .step tspan{fill:#333!important;} 第 1 步
理解横切关注点
(代码重复)
第 2 步
本机写 @Log 切面
跑通第一个 AOP
第 3 步
看公司项目里
别人写的切面
第 4 步
debug 切面执行
(断点 + log)
第 5 步
尝试自己写
业务切面
第 6 步
踩坑 + 复盘
(同类调用/Order)
- 第 1 步:先理解"为什么要 AOP"------业务代码被日志/锁/权限淹没
- 第 2 步 :本机写一个
@Log切面,跑通第一个 AOP - 第 3 步:看公司项目里别人写的切面,理解"注解 + 切面"是一对
- 第 4 步:debug 切面执行流程(加 log / 断点跟进去看)
- 第 5 步:尝试自己写一个业务切面(比如缓存切面、限流切面)
- 第 6 步:踩坑 + 复盘(同类调用 / 顺序 / 异常)
💡 过来人的金句 :先跑通,再读原理;先会用,再调优。 AOP 的高级特性(编译期织入、
@DeclareParents)都是"会用之后的优化项"。
八、结尾
写到这里,这篇博客已经 5500+ 字了。最后做个小结:
AOP 不可怕,它就是"把重复代码抽出来"的一种工程化手段。
- 实习生要学的:能看懂别人写的切面、能给业务方法加注解
- 中级要学的:自己写切面(注解 + 切面类)、处理多切面顺序
- 高级要学的 :理解代理原理、知道
AopContext、能 review 切面代码
最后送大家一句话,也是 mentor 告诉我的:
好的 AOP 不是"什么都能切",而是"只解决业务代码不该重复写的痛点"。
📮 评论区聊聊:你写的第一个 AOP 切面是用来干嘛的?踩过什么坑?
我是 程序员小八777 ,一个喜欢把实习踩过的坑写成博客的计算机博主。如果这篇博客对你有帮助,欢迎 点赞 👍、收藏 ⭐、关注 🔔 ------你的支持就是我继续肝下去的最大动力!
下期预告:《实习中我是怎么用 AOP + Redis 写一个通用幂等切面的》
📚 推荐阅读
- 📖 Spring 官方文档 - AOP --- 最权威的入门资料
- 📖 Spring AOP 5 种通知执行顺序详解 --- 官方对通知类型的说明
- ⭐ Spring AOP @Pointcut 表达式指南 --- 切入点语法大全
- 📺 黑马程序员 Spring AOP 教程(B 站) --- 中文视频,适合快速入门
- 📖 AspectJ 官方文档 --- 想深入了解织入原理时看
- 📖 Baeldung - Spring AOP 教程 --- 英文,但例子非常全