Spring AOP详解(一)

一、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匹配:

实现步骤:

  1. 编写⾃定义注解
  2. 使⽤ @annotation 表达式来描述切点
  3. 在连接点的⽅法上添加⾃定义注解

步骤一:⾃定义注解@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 注解

参考:谈谈你对IOC和AOP的理解及AOP四种实现方式

@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 是 "划范围拦截"(直接划包 / 类范围→拦截范围内所有方法)。

相关推荐
Wang15302 小时前
Java多线程死锁排查
java·计算机网络
web小白成长日记2 小时前
在Vue样式中使用JavaScript 变量(CSS 变量注入)
前端·javascript·css·vue.js
MoonBit月兔2 小时前
年终 Meetup:走进腾讯|AI 原生编程与 Code Agent 实战交流会
大数据·开发语言·人工智能·腾讯云·moonbit
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之which命令(实操篇)
linux·运维·服务器·前端·chrome·笔记
智航GIS2 小时前
8.2 面向对象
开发语言·python
C_心欲无痕2 小时前
react - useImperativeHandle让子组件“暴露方法”给父组件调用
前端·javascript·react.js
小小星球之旅2 小时前
CompletableFuture学习
java·开发语言·学习
利刃大大3 小时前
【SpringBoot】Spring事务 && @Transactional详解 && Spring事务失效问题
spring boot·spring·事务
jiayong233 小时前
知识库概念与核心价值01
java·人工智能·spring·知识库