当前位置: 首页 > news >正文

SpringAOP:面向切面编程

大家好,这依旧是一篇个人笔记,关于SpringAOP的笔记

笔记内容来源于黑马Web课程133~139集

本篇笔记由WorkBuddy一起辅助完成,不喜勿喷,谢谢!

一、Spring AOP 整体思路

Spring AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想,核心思想是:在不修改原有业务代码的情况下,为指定的方法统一添加额外的通用功能。

1.1 AOP 核心思想

AOP 的本质是「面向方法编程」,通过「切面」将「额外功能」与「作用范围」结合起来:

  • • 切面(Aspect):额外功能 + 作用范围的组合
  • • 额外功能:如统计耗时、日志记录、权限校验等通用逻辑
  • • 作用范围:通过切入点表达式指定哪些方法需要添加额外功能

总结:AOP编程思想“面向切面”或者“面向方法”编程本质上就是给“你规定的一系列方法”加上“额外功能”

所谓“切面”就是指“额外功能”+“作用范围”(就是我规定的哪些方法)

以上面图片为例:本质上就是给“业务层的方法”添加“计算耗时”这个额外功能

1.2 AOP 执行流程

AOP 的执行基于「动态代理」机制:

步骤

说明

1. 定义切面类

使用 @Aspect 注解标识切面类,@Component 纳入 Spring 管理

2. 定义切入点

使用 @Pointcut 或直接在通知注解中指定目标方法范围

3. 定义通知

使用 @Around/@Before 等注解定义额外功能的执行时机

4. 生成代理对象

Spring 为目标对象创建代理对象(JDK 动态代理或 CGLIB)

5. 调用代理方法

Controller 实际调用的是代理对象的方法

6. 执行通知逻辑

代理对象先执行通知逻辑,再调用目标方法

二、Spring AOP 基础

2.1 AOP 快速入门

2.1.1 场景需求

统计所有业务层方法的执行耗时,用于性能分析和优化。

2.1.2 实现步骤

步骤一:引入 AOP 依赖(pom.xml)

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

步骤二:编写切面类

@Aspect // 标明这是一个 AOP 类 @Component // 纳入 Spring 容器管理 public class RecordTimeAspect { @Around("execution(* com.itheima.service.impl.*.*(..))") public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { // 1. 记录开始时间 long begin = System.currentTimeMillis(); // 2. 执行目标方法 Object result = pjp.proceed(); // 3. 记录结束时间并计算耗时 long end = System.currentTimeMillis(); log.info("方法执行耗时: {} ms", end - begin); return result;

2.2 AOP 核心概念

概念

说明

连接点 (JoinPoint)

可以被 AOP 控制的方法,包含方法执行时的相关信息

通知 (Advice)

需要添加的额外功能(共性逻辑),最终体现为一个方法

切入点 (PointCut)

匹配连接点的条件,通知仅在切入点方法执行时被应用

切面 (Aspect)

描述通知与切入点的对应关系(通知 + 切入点)

目标对象 (Target)

通知所应用的对象(被代理的原始对象)

2.3 AOP 的优势

  • • 减少重复代码:通用逻辑统一维护
  • • 代码无侵入:不修改原有业务代码
  • • 提高开发效率:复用通用功能
  • • 维护方便:修改一处,全局生效

三、Spring AOP 进阶

3.1 通知类型

根据通知方法执行时机的不同,分为以下五类:

通知类型

执行时机

特点

@Around

目标方法前、后都执行

环绕通知,需手动调用 proceed(),最常用

@Before

目标方法前执行

前置通知

@After

目标方法后执行

后置通知,无论是否异常都执行

@AfterReturning

目标方法正常返回后执行

返回后通知,有异常不执行

@AfterThrowing

目标方法抛出异常后执行

异常后通知

⚠️ 注意事项:

  • • @Around 环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来执行目标方法
  • • @Around 环绕通知方法的返回值必须指定为 Object,用于接收目标方法的返回值

3.2 通知顺序

3.2.1 默认顺序

当多个切面的切入点都匹配到目标方法时,默认按照切面类的「类名字母顺序」排序:

  • • 目标方法前的通知:字母排名靠前的先执行
  • • 目标方法后的通知:字母排名靠前的后执行

3.2.2 自定义顺序(@Order)

使用 @Order(数字) 注解控制执行顺序,数字越小越先执行:

@Order(5) // 先执行 @Aspect @Component public class MyAspect3 {@Before("execution(* com.itheima.service.impl.*.*(..))")public void before(){ log.info("MyAspect3 -> before ..."); } @Order(8) // 后执行 @Aspect @Component public class MyAspect2 {@Before("execution(* com.itheima.service.impl.*.*(..))")public void before(){ log.info("MyAspect3 -> before ..."); }

3.3 切入点表达式

3.3.1 execution 表达式

execution 是最常用的切入点表达式,语法如下:

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

通配符说明:

通配符

含义

*

单个独立的任意符号,可匹配任意返回值、包名、类名、方法名、参数

..

多个连续的任意符号,可匹配任意层级的包或任意个数、类型的参数

常用示例:

// 匹配 service 包下所有类的所有方法 execution(* com.itheima.service.*.*(..)) // 匹配所有以 del 开头的方法 execution(* com.itheima.service.*.del*(..)) // 匹配所有以 e 结尾的方法 execution(* com.itheima.service.*.*e(..)) // 同时匹配多个方法(使用 || 运算符) execution(* com.itheima.service.*.list(..)) || execution(* com.itheima.service.*.delete(..))

书写建议:

  • • 所有业务方法名命名时尽量规范,方便切入点表达式快速匹配
  • • 描述切入点方法通常基于接口描述,增强拓展性
  • • 在满足业务需要的前提下,尽量缩小切入点的匹配范围

3.3.2 @annotation 表达式

基于自定义注解标记方法,而非直接写方法路径,更加灵活:

补充一下关于annotation切入点表达式的理解:本质上就是定义了一个自己想要的注解,然后再切面类的通知方法里面绑定归属于哪一个自己定义的注解

步骤一:定义自定义注解

步骤二:切面类绑定注解

步骤三:在目标方法上使用注解

3.3.3 @PointCut 抽取公共切入点

将公共的切入点表达式抽取出来,提高复用性:

PointCut:对于公共切入点(方法路径)的简化;作用就像是springboot里面RequestMapping公共路径书写的作用

@Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt() {} // private:仅当前切面类可用 @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { // 通知逻辑 }

注意:

• private:仅能在当前切面类中引用该表达式

• public:在其他外部的切面类中也可以引用该表达式

3.4 连接点 JoinPoint

就是你要在Aspect类里面执行原方法的内容就必须用到这个JointPoint

在 Spring 中用 JoinPoint 抽象了连接点,可以获得方法执行时的相关信息:

API 方法

说明

joinPoint.getTarget()

获取目标对象

joinPoint.getTarget().getClass().getName()

获取目标类全名

joinPoint.getSignature().getName()

获取目标方法名

joinPoint.getArgs()

获取目标方法参数数组

joinPoint.proceed()

执行目标方法(仅 ProceedingJoinPoint 可用)

注意:

  • 对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint
  • • 对于其他四种通知,获取连接点信息使用 JoinPoint(ProceedingJoinPoint 的父类)

四、Spring AOP 案例

4.1 案例:记录操作日志

将增、删、改相关接口的操作日志记录到数据库表中。

4.1.1 日志信息内容

  • • 操作人:当前登录员工的 ID
  • • 操作时间:方法执行的时间
  • • 执行方法的全类名、方法名
  • • 方法运行时参数、返回值
  • • 方法执行时长

4.1.2 技术方案

项目

选择

通知类型

@Around 环绕通知

切入点表达式

@annotation(com.example.tlias_management.anno.Log)

4.2 案例:获取当前登录员工

通过 ThreadLocal 在拦截器和切面之间传递当前登录员工信息。

4.2.1 实现思路

输出操作人实现思路:操作的时候需要登陆认证,会产生JWT令牌,之前我们定义的JWT令牌自定义了信息“id”,而这个id可以唯一标识每一个员工,因此获取到这个id就相当于知道操作人是谁了

  • • 登录时生成 JWT 令牌,包含员工 ID
  • • 拦截器解析 Token,将员工 ID 存入 ThreadLocal
  • • 切面类从 ThreadLocal 获取操作人 ID
  • • 请求结束后清除 ThreadLocal(防止内存泄漏)

4.2.2 ThreadLocal 工具类

先准备这个线程类,每次请求的数据都会保存在独立的一次线程中,保证了获取到的数据就是那次请求的数据

4.2.3 拦截器设置 ThreadLocal

@Component public class TokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("token"); Claims claims = JwtUtils.parseToken(token); Integer employeeId = Integer.valueOf(claims.get("id").toString()); CurrentHolder.setCurrentId(employeeId); // 存入 ThreadLocal return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { CurrentHolder.remove(); // 清除 ThreadLocal } }

4.2.4 切面类获取操作人

@Around("@annotation(com.example.tlias_management.anno.Log)") public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable { // 从 ThreadLocal 获取操作人 ID Integer operateEmpId = CurrentHolder.getCurrentId(); // 其他日志信息... LocalDateTime operateTime = LocalDateTime.now(); String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); // 执行目标方法 Object result = joinPoint.proceed(); // 保存日志到数据库... return result;

五、知识总结

5.1 核心概念回顾

概念

一句话总结

AOP

面向切面编程,不修改原代码给方法加额外功能

切面 (Aspect)

通知 + 切入点的组合

通知 (Advice)

要添加的额外功能

切入点 (PointCut)

指定哪些方法需要添加通知

连接点 (JoinPoint)

可以被 AOP 控制的方法

目标对象 (Target)

被代理的原始对象

5.2 常用注解

注解

作用

@Aspect

标识这是一个切面类

@Component

将切面类纳入 Spring 容器管理

@Around/@Before/@After

定义通知类型和执行时机

@Pointcut

抽取公共切入点表达式

@Order

控制多个切面的执行顺序

5.3 切入点表达式

类型

使用场景

execution

按方法路径匹配,适合批量匹配

@annotation

按自定义注解匹配,更加灵活精准

5.4 注意事项

  • • @Around 必须调用 proceed() 执行目标方法
  • • @Around 返回值必须是 Object
  • • 使用 ThreadLocal 后一定要在 afterCompletion 中 remove()
  • • 切入点表达式尽量缩小匹配范围,提高性能
http://www.jsqmd.com/news/659821/

相关文章:

  • 环境配置地狱终结者:DevContainer实战避坑手册
  • GLM-OCR部署性能调优:CUDA Graph启用+KV Cache优化降低首token延迟
  • Qwen3.5-9B镜像部署全攻略:开箱即用,体验强逻辑推理与多模态理解
  • WechatDecrypt微信聊天记录解密工具:3步轻松恢复加密数据
  • 微信立减金套装回收是真的吗?表妹的经历让我恍然大悟 - 京顺回收
  • TranslucentTB透明任务栏:Windows 10/11系统美化实战解决方案
  • 空气解决方案提供商Madison Air纽交所上市:募资22亿美元 市值155亿美元
  • 教育场景落地:FireRedASR-AED-L实现英语口语自动批改
  • P2257 学习笔记
  • 从产品质量到用户评分:聊聊高斯分布在A/B测试、推荐系统等业务场景中的实战应用与误区
  • JVM内存模型与垃圾回收全解析
  • 福州市凤玖建筑工程有限公司:晋安区工装附近公司 - LYL仔仔
  • 智能代码生成安全风险评估:2024年Q2最新NIST SP 800-218适配指南,含3类模型权重级风险分级矩阵(L1-L3)
  • 番茄小说下载器终极指南:3种方法实现离线阅读与格式转换
  • 2026年给排水行业公司排名:江苏华厦给排水是否有自主知识产权,好用吗 - 工业设备
  • 5步掌握Windows任务栏透明化:用TranslucentTB轻松实现个性化桌面
  • Windows Cleaner:三步彻底解决C盘爆红问题,让电脑重获新生!
  • Anthropic发现:人工智能会成为隐藏自己真实意图的“卧底”吗?
  • 2026终极指南:3种方法轻松重置JetBrains IDE试用期
  • 成都市蜀宏吊装工程有限责任公司:成都市设备吊装搬运服务 - LYL仔仔
  • 梳理有实力的工业除尘滤筒大型厂家,选购攻略分享 - 工业品牌热点
  • 谷歌 Chrome 浏览器大升级:全新搜索体验,三项新功能让信息研究更便捷!
  • 上交大、中科大联合研究:AI监督微调真的“只会死记硬背“吗?
  • JetBrains IDE试用期重置:技术原理与专业实践指南
  • iOS逆向初体验:不用越狱,用MonkeyDev+Logos给App“加功能”
  • 从555振荡器到74LS192:手把手构建一个带整点报时的数字电子时钟
  • 东北大学与麻省理工学院联手破解AI“黑箱“
  • Scroll Reverser深度解析:重新定义你的macOS滚动体验
  • 揭秘兴达净化实力,其除尘滤芯反馈好吗及价格多少钱 - 工业推荐榜
  • Claude 4编码能力实战指南:OPC开发者的工具链升级方案