一、AOP概述
1. AOP 的核心概念
- AOP 定义 :Aspect Oriented Programming(面向切面编程),是一种 "对某一类事情集中处理" 的思想,比如登录校验、统一异常处理等场景。
- AOP 与拦截器的关系:拦截器是 AOP 思想的一种实现(如登录校验拦截器),而 AOP 是更通用的思想。
2. Spring AOP 的定位
- AOP 是思想,有多种实现方式(如 Spring AOP、AspectJ、CGLIB),Spring AOP 是其中一种实现。
- Spring AOP 的优势:作用维度更细致(可按包、类、方法名、参数等拦截),能实现更复杂的业务逻辑(区别于拦截器仅基于 URL 维度、
@ControllerAdvice仅处理全局异常等场景)。
3. AOP 的典型应用场景(以方法耗时统计为例)
- 问题:若要统计多个业务方法的执行耗时,逐个修改方法会增加大量工作量。


- AOP 的价值:在不修改原始代码的前提下,对特定方法进行功能增强(无侵入性、解耦),例如在方法执行前后自动记录时间并计算耗时。
AOP 的核心作用
在程序运行期间,不修改源码的情况下对已有方法进行增强,实现 "统一处理一类逻辑" 的目的。
二、SpringAOP快速⼊⻔
1.引⼊AOP依赖

2.编写AOP程序
记录Controller中每个⽅法的执⾏时间

- @Aspect:标识这是⼀个切⾯类
- @Around:环绕通知,在⽬标⽅法的前后都会被执⾏.后⾯的表达式表⽰对哪些⽅法进⾏增强.
- ProceedingJoinPoint.proceed()让原始⽅法执⾏

- 代码⽆侵⼊:不修改原始的业务⽅法,就可以对原始的业务⽅法进⾏了功能的增强或者是功能的改变
- 减少了重复代码
- 提⾼开发效率
- 维护⽅便
三、AOP详解
1.SpringAOP核⼼概念
1. 1切点(Pointcut)
- 定义 :是一组 "规则"(用 AspectJ 表达式描述),用来告诉程序 "要对哪些方法进行功能增强"。
- 示例 :代码中的
execution(* com.example.demo.controller.*.*(..))就是切点表达式 ,它的作用是 "指定拦截com.example.demo.controller包下所有类的所有方法"。 - 本质 :是 "筛选条件",用来圈定需要被 AOP 处理的方法范围。

1.2 连接点(Join Point)
- 定义 :满足切点规则的方法,也就是 "可以被 AOP 控制的方法"。
- 示例 :
BookController中的addBook()、queryBookById()、updateBook()等方法,因为符合 "controller 包下的方法" 这一切点规则,所以都是连接点。 - 本质 :是 "被切点选中的具体方法",是 AOP 实际作用的对象。


切点与连接点的关系
- 切点:用表达式写的 "筛选规则"(比如 "所有 service 层方法")。
- 连接点 :符合这个规则的 "具体方法"(比如
UserService.get())。 - 切点表达式:写 "筛选规则" 的语言。
一句话概括:切点是 "选谁" 的规则,连接点是 "被选中的那个方法",表达式是写规则的工具。
1.3通知(Advice)
- 定义 :是 AOP 要执行的具体逻辑(比如 "统计方法耗时"),最终体现为一个方法。
- 本质 :是 "重复业务逻辑的抽离"------ 把原本要写在多个方法里的相同代码 (比如每个方法都写 "记录开始 / 结束时间"),单独抽成一个通知方法。
- 示例 :代码中
recordTime()方法里的 "记录开始时间、执行原方法、计算耗时" 这部分逻辑,就是通知。

1.4切面(Aspect)
- 定义 :
切面 = 切点 + 通知,是 AOP 的 "完整执行单元"。 - 作用:描述 "对哪些方法(切点)、执行什么样的操作(通知)"。
- 载体 :被
@Aspect注解标记的类(比如TimeAspect类),就是切面类 ------ 它同时包含了 "要拦截的方法规则(切点)" 和 "要执行的逻辑(通知)"。

核心关系总结
- 通知是 "做什么"(具体逻辑);
- 切点是 "对谁做"(目标方法范围);
- 切面是 "把'做什么'和'对谁做'绑定在一起" 的单元。
比如TimeAspect类:
- 切点是
execution(* com.example.demo.controller.*.*(..))(对 controller 包下的方法); - 通知是 "统计耗时的逻辑"(做耗时统计);
- 切面就是 "对 controller 包下的方法执行耗时统计" 这个完整的功能。
一句话记牢
切面 = 切点(定范围) + 通知(做逻辑),切点表达式是定义切点的 "语言工具"。
举个生活化例子:
- 切点(表达式):"所有班级的数学课"(划定范围);
- 通知:"上课前点名"(要做的逻辑);
- 切面:"对所有班级的数学课执行上课前点名"(完整规则)。
✅ 切面类 = Spring 管理(@Component) + AOP 标识(@Aspect) + 切点(范围) + 通知(逻辑 + 类型)
2.通知类型
2.1 5 种通知类型及作用
| 通知类型 | 执行时机 | 异常时是否执行 |
|---|---|---|
@Before |
目标方法执行前 | 是 |
@After |
目标方法执行后(无论是否异常) | 是 |
@AfterReturning |
目标方法正常执行完成后(无异常) | 否 |
@AfterThrowing |
目标方法抛出异常后 | 仅异常时执行 |
@Around |
目标方法执行前后 (需手动调用proceed()) |
是(可捕获异常 |


1.正常执行时的顺序
以test/t1接口(无异常)为例,执行顺序为:@Around(前半部分) → @Before → 目标方法 → @AfterReturning → @After → @Around(后半部分)

对应日志输出:

2.异常时的变化
以test/t2接口(含10/0异常)为例:
@AfterReturning不会执行;@AfterThrowing会触发;- 最终顺序变为:
@Around前→@Before→ 目标方法(抛异常) →@AfterThrowing→@After→@Around后(若未捕获异常则不执行)

@Around是功能最强的通知,能完全控制目标方法的执行(需调用proceed());@After是 "最终通知",无论是否异常都会执行(常用于资源释放)。
注意:
不是只有 @Around 能执行原方法,而是所有通知类型下,原方法都会自动执行(无需你手动操作);@Around 只是 "能控制原方法是否执行",而其他通知类型 "无法干预原方法执行"。
1. 非 @Around 通知(Before/After/AfterReturning/AfterThrowing)
- 执行逻辑:Spring 会自动先执行你的通知逻辑,再执行原方法(或按对应时机执行),你完全不用写任何代码触发原方法。比如:

- 特点:你只能 "观察 / 增强",无法阻止原方法执行(哪怕你想让原方法不执行,也做不到)。
2. @Around 通知
- 执行逻辑:原方法是否执行,完全由你控制 ------ 必须手动调用
pjp.proceed()才会执行原方法;如果不调用,原方法就不会执行。比如:

- 特点:拥有对原方法的 "控制权"(执行 / 不执行 / 多次执行都可以),这是 @Around 和其他通知的核心区别。
通俗类比
- 其他通知:你是 "观众",只能在演员(原方法)上场前 / 下场后鼓掌(执行通知逻辑),无法阻止演员表演;
- @Around:你是 "导演",可以决定演员要不要上场(调用 proceed ())、什么时候上场,甚至让演员演两遍。
总结
| 通知类型 | 原方法是否自动执行 | 能否控制原方法执行 |
|---|---|---|
| Before/After 等 | ✅ 自动执行 | ❌ 无法控制 |
| Around | ❌ 需手动调用 proceed () | ✅ 完全控制 |
3.@PointCut
3.1 作用
解决 "多个通知重复写相同切点表达式" 的问题,将公共切点表达式提取为单独的方法,后续通知直接引用该方法即可。
3.2.用法

3. 3跨切面复用
若要在其他切面类中引用,需将切点方法改为public,引用时写全限定类名。方法名:
上述代码就可以修改为:

4 切⾯优先级@Order
当多个切面匹配同一目标方法时,需通过@Order控制执行顺序。
4.1.默认顺序
按切面类名的字母顺序执行:
@Before通知:类名字母靠前的先执行;@After通知:类名字母靠前的后执行。
4.2 @Order 的用法

通过@Order(数字)指定优先级,数字越小,优先级越高:
执行规则
@Before通知:@Order数字小的先执行;@After通知:@Order数字大的先执行;- 整体流程:高优先级切面的
@Before→ 低优先级切面的@Before→ 目标方法 → 低优先级切面的@After→ 高优先级切面的@After。
假设有 2 个切面:
- 切面 A:
@Order(1)(高优先级),包含@Before(A前置)+@After(A后置); - 切面 B:
@Order(2)(低优先级),包含@Before(B前置)+@After(B后置)。
最终执行顺序:A前置 → B前置 → 目标方法 → B后置 → A后置
关键补充
@Order只控制不同切面之间的执行顺序 ,同一个切面内的多个通知(比如一个切面里既有@Before又有@After),执行顺序固定:@Before先跑,@After后跑,和@Order无关。
价值
@PointCut减少重复代码,提升维护性;@Order让多切面的执行顺序可预测,避免依赖默认类名字母顺序的不可控性
5.切点表达式
5.1切点表达式的两种类型
| 类型 | 作用 | 适用场景 |
|---|---|---|
execution(...) |
按 ** 方法签名(包、类、方法名、参数)** 匹配 | 方法有统一命名 / 包路径规则的场景 |
@annotation(...) |
按方法上的注解匹配 | 匹配无规则的零散方法(通过自定义注解标记) |
5.2execution表达式
(访问修饰符、异常可省略)



| 匹配范围 | execution 表达式 | 核心通配符用法 |
|---|---|---|
| 单个方法(精准匹配) | execution(String com.example.demo.controller.TestController.t1()) |
无通配符,指定完整方法签名 |
| 单个类的无参方法 | execution(* com.example.demo.controller.TestController.*()) |
用*匹配方法名,()匹配无参 |
| 单个类的所有方法 | execution(* com.example.demo.controller.TestController.*(..)) |
用(..)匹配任意参数 |
| 某个包下所有类的所有方法 | execution(* com.example.demo.controller.*.*(..)) |
用*匹配包下类、类下方法 |
| 某个包及子包的所有方法 | execution(* com.example.demo..*.*(..)) |
用..匹配包及子包 |
5.3@annotation
当方法无统一规则时,可自定义注解标记目标方法,再通过@annotation匹配:
实现步骤:
- 编写⾃定义注解
- 使⽤ @annotation 表达式来描述切点
- 在连接点的⽅法上添加⾃定义注解
步骤一:⾃定义注解@MyAspect
创建⼀个注解类(和创建Class⽂件⼀样的流程,选择Annotation就可以了)


两个核心元注解的作用
元注解是 "修饰注解的注解",决定了自定义注解的使用范围 和生命周期:
| 元注解 | 作用说明 |
|---|---|
@Target |
限定注解的使用位置 ,ElementType.METHOD表示该注解只能用在方法上 (常用值还有TYPE(类 / 接口)、PARAMETER(参数)等)。 |
@Retention |
定义注解的生命周期 ,RetentionPolicy.RUNTIME表示注解在运行时仍存在(可通过反射获取),这是 Spring AOP 中自定义注解的必要配置(因为 AOP 需要在运行时识别注解)。 |
不同 @Retention 值的区别
| Retention 取值 | 生命周期范围 | 典型场景 |
|---|---|---|
SOURCE |
仅存在于源码,编译后丢弃 | Lombok 注解(如 @Data、@Slf4j) |
CLASS |
存在于源码和字节码,运行时丢弃 | 部分框架编译期注解 |
RUNTIME |
存在于源码、字节码、运行时 | Spring 注解(如 @Controller) |
步骤二 :写切面类,用@annotation绑定注解
使⽤ @annotation 切点表达式定义切点,只对@MyAspect ⽣效

步骤三:给目标方法加 @MyAspect 注解

@annotation 实现 AOP 的核心步骤(对比 execution)
| 步骤 | @annotation(注解匹配) | execution(包 / 类匹配) |
|---|---|---|
| 1. 前置准备 | ✅ 创建自定义注解(加 @Target+@Retention) | ❌ 无需创建注解 |
| 2. 定义切面类 | ✅ 加 @Aspect+@Component(和 execution 完全一样) | ✅ 加 @Aspect+@Component |
| 3. 写切点表达式 | ✅ @Before ("@annotation (注解全类名)")(只拦截加了注解的方法) | ✅ @Before ("execution (包 / 类表达式)")(拦截包 / 类下所有方法) |
| 4. 目标方法配置 | ✅ 给需要拦截的方法加自定义注解(如 @MyAspect) | ❌ 无需配置,自动匹配表达式范围 |
- 写
@annotation(...)时,必须写注解的全类名 (比如com.example.aop.aspect.MyAspect),如果切面类和注解类在同一个包下,也可以简写为@annotation(MyAspect); - 自定义注解的
@Target如果写错(比如写成ElementType.TYPE),加在方法上会报错,需严格匹配使用位置。
@annotation 是 "贴标签拦截"(先建标签→贴标签→拦截贴了标签的方法),execution 是 "划范围拦截"(直接划包 / 类范围→拦截范围内所有方法)。