【Spring】小白速通AOP-日志记录Demo

这篇文章我将通过一个最常用的AOP场景-方法调用日志记录,带你彻底理解AOP的使用。例子使用Spring Boot+Spring AOP实现。
如果对你有帮助可以点个赞和关注。谢谢大家的支持!!

一、Demo实操步骤:

1.首先添加Maven依赖

<!-- Spring AOP支持 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.创建日志切面类

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Aspect     // 标识这是一个切面类
@Component  // 让Spring能够扫描到这个组件
public class LoggingAspect {// 创建日志记录器private final Logger logger = LoggerFactory.getLogger(this.getClass());/*** 定义切点:拦截service包下的所有方法* execution(返回值类型 包名.类名.方法名(参数列表))*/@Pointcut("execution(* com.example.demo.service.*.*(..))")public void serviceLayer() {}/*** 前置通知:在目标方法执行前执行*/@Before("serviceLayer()")public void logBefore(JoinPoint joinPoint) {// joinPoint包含目标方法的信息logger.info("准备执行 {} 方法", joinPoint.getSignature().getName());logger.info("参数: {}", Arrays.toString(joinPoint.getArgs()));}/*** 后置通知:在目标方法正常返回后执行*/@AfterReturning(pointcut = "serviceLayer()", returning = "result")public void logAfterReturning(JoinPoint joinPoint, Object result) {logger.info("方法 {} 执行成功,返回值: {}", joinPoint.getSignature().getName(), result);}/*** 异常通知:在目标方法抛出异常后执行*/@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {logger.error("方法 {} 执行异常: {}", joinPoint.getSignature().getName(), ex.getMessage());}/*** 环绕通知:最强大的通知类型,可以控制方法执行*/@Around("serviceLayer()")public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();logger.info("进入方法: {}", joinPoint.getSignature().getName());try {// 执行目标方法Object result = joinPoint.proceed();long elapsedTime = System.currentTimeMillis() - start;logger.info("方法执行完成,耗时: {} ms", elapsedTime);return result;} catch (Exception e) {long elapsedTime = System.currentTimeMillis() - start;logger.error("方法执行异常,耗时: {} ms,异常: {}", elapsedTime, e.getMessage());throw e;}}
}

3.创建测试服务类

@Service
public class UserService {public String getUserById(Long id) {// 模拟数据库查询if (id == 1) {return "用户张三";} else if (id == 2) {return "用户李四";}throw new RuntimeException("用户不存在");}public void updateUser(String user) {// 模拟更新操作System.out.println("更新用户: " + user);}
}

4.测试Controller

@RestController
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/user/{id}")public String getUser(@PathVariable Long id) {return userService.getUserById(id);}@PostMapping("/user")public void updateUser(@RequestBody String user) {userService.updateUser(user);}
}

二、代码讲解:

1.切面(Aspect):

LoggingAspect类 就是一个切面,它封装了横切关注点(这里是日志记录)。

2.切点(Pointcut):

@Pointcut("execution(* com.example.demo.service.*.*(..))")
  • 定义了"哪些方法需要被拦截"
  • execution是匹配方法执行的连接点
  • *表示任意返回值
  • com.example.demo.service指定包名
  • 第一个*表示所有类
  • 第二个*表示所有方法
  • (. .)表示任意参数

3.通知(Advice):

  • @Before方法执行前
  • @AfterReturning:方法正常返回后
  • @AfterThrowing:方法抛出异常后
  • @Around:最强大,可以控制方法是否执行

三、执行流程演示:

场景1:调用GET/user/1

1.请求进入UserController.getUser(1)
2.调用userService.getUserById(1)时被AOP拦截
3.执行顺序:

  • @Around的开始部分(记录开始时间)
  • @Before(记录方法准备执行)
  • 实际执行getUserById方法
  • @AfterReturning(记录成功返回)
  • @Around的结束部分(计算耗时)

控制台输出 执行结果:

进入方法: getUserById
准备执行 getUserById 方法
参数: [1]
方法 getUserById 执行成功,返回值: 用户张三
方法执行完成,耗时: 12 ms

场景2.调用GET/user/3(会抛出异常)

1.请求进入UserController.getUser(3)
2.调用userService.getUserById(3)时被AOP拦截
3.执行顺序:

  • @Around的开始部分
  • @Before
  • 执行getUserById抛出异常
  • @AfterThrowing(记录异常信息)
  • @Around的异常处理部分

控制台输出 执行结果:

进入方法: getUserById
准备执行 getUserById 方法
参数: [3]
方法 getUserById 执行异常: 用户不存在
方法执行异常,耗时: 5 ms,异常: 用户不存在

四、为什么要用AOP?

对比传统写法,如果不用AOP,我们需要在每个方法中写日志代码,重复代码太多,显得冗杂。

@Service
public class UserService {private final Logger logger = LoggerFactory.getLogger(this.getClass());public String getUserById(Long id) {long start = System.currentTimeMillis();logger.info("准备执行 getUserById 方法");logger.info("参数: " + id);try {if (id == 1) {String result = "用户张三";logger.info("方法执行成功,返回值: " + result);return result;}throw new RuntimeException("用户不存在");} catch (Exception e) {logger.error("方法执行异常: " + e.getMessage());throw e;} finally {long elapsedTime = System.currentTimeMillis() - start;logger.info("方法耗时: " + elapsedTime + " ms");}}
}

此时就可以使用AOP来解决。

AOP的优势:

1.消除重复代码:日志逻辑只写一次,应用到所有方法。
2.业务更纯净:业务方法只关注核心逻辑。
3.维护方便:修改日志格式只需改切面类。
4.灵活扩展:可以随时添加新的横切逻辑(如权限检查)。

五、应用场景:

1. 统一日志记录(如上面的例子)

2.性能监控:统计方法执行时间

@Around("serviceLayer()")
public Object monitorPerformance(ProceedingJoinPoint jp) throws Throwable {long start = System.currentTimeMillis();Object result = jp.proceed();long elapsed = System.currentTimeMillis() - start;if (elapsed > 500) { // 超过500ms记录警告logger.warn("方法 {} 执行耗时 {} ms", jp.getSignature(), elapsed);}return result;
}

3.事务管理(Spring的@Transactional就是基于AOP实现的)

4.权限控制:

@Before("@annotation(requiresAuth)")
public void checkAuth(JoinPoint jp, RequiresAuth requiresAuth) {if (!SecurityUtils.hasRole(requiresAuth.value())) {throw new AccessDeniedException("无权限访问");}
}

5.缓存处理:

@Around("@annotation(cacheable)")
public Object handleCache(ProceedingJoinPoint jp, Cacheable cacheable) throws Throwable {String key = generateKey(jp, cacheable);Object cached = cache.get(key);if (cached != null) return cached;Object result = jp.proceed();cache.put(key, result);return result;
}

六、小白常见问题:

1.切面不生效?

  • 确保切面类@Component 注解。
  • 确保启动类有 @EnableAspectJAutoProxy(Spring Boot自动配置)
  • 检查切点表达式是否正确匹配到目标方法。

2.执行顺序问题:

  • 多个切面同一个切点,可以用 @Order注解指定顺序。
  • 单个切面中通知执行顺序:Around前 → Before → 方法执行 → Around后 → AfterReturning/AfterThrowing → After

3.自调用问题:

  • 同一个类内部方法调用不会触发AOP(因为不是代理对象调用)
  • 解决方案:从Spring容器获取代理对象调用

七、总结:

通过这篇文章,你应该理解了:

1.AOP如何通过切面、切点、通知等概念工作。
2.为什么AOP能解决代码重复的问题。
3.如何在Spring项目中实际使用AOP。
4.AOP的各种实际应用场景。

AOP就像是一个"方法拦截器",在不修改原有代码的情况下,给方法添加各种增强功能。这是Spring框架的核心特性之一,掌握后能大幅提高代码质量和开发效率。

看到这里,如果觉得这篇文章对你有帮助,能给up主点个赞嘛,编写不易,谢谢大家的支持与鼓励!!

下篇文章挖掘AOP代码实现中的一些细节问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/77106.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git功能点管理

需求&#xff1a; 功能模块1 已经完成&#xff0c;已经提交并推送到远程&#xff0c;准备交给测试。功能模块2 已经完成&#xff0c;但不提交给测试&#xff0c;继续开发。功能模块3 正在开发中。 管理流程&#xff1a; 创建并开发功能模块1&#xff1a; git checkout main…

QGIS实战系列(六):进阶应用篇——Python 脚本自动化与三维可视化

欢迎来到“QGIS实战系列”的第六期!在前几期中,我们从基础操作到插件应用逐步提升了 QGIS 技能。这一篇,我们将迈入进阶领域,探索如何用 Python 脚本实现自动化,以及如何创建三维可视化效果,让你的 GIS 项目更高效、更立体。 第一步:Python 脚本自动化 QGIS 内置了 Py…

高德地图 3D 渲染-区域纹理图添加

引入-初始化地图&#xff08;关键代码&#xff09; // 初始化页面引入高德 webapi -- index.html 文件 <script src https://webapi.amap.com/maps?v2.0&key您申请的key值></script>// 添加地图容器 <div idcontainer ></div>// 地图初始化应该…

ffmpeg视频转码相关

ffmpeg视频转码相关 简介参数 实战举栗子获取视频时长视频转码mp4文件转为hls m3u8 ts等文件图片转视频抽取视频第一帧获取基本信息 转码日志输出详解转码耗时测试 简介 FFmpeg 是领先的多媒体框架&#xff0c;能够解码、编码、 转码、复用、解复用、流、过滤和播放 几乎所有人…

【ISP】HDR技术中Sub-Pixel与DOL的对比分析

一、原理对比 Sub-Pixel&#xff08;空间域HDR&#xff09; • 核心机制&#xff1a;在单个像素内集成一大一小两个子像素&#xff08;如LPD和SPD&#xff09;&#xff0c;利用其物理特性差异&#xff08;灵敏度、满阱容量&#xff09;同时捕捉不同动态范围的信号。 ◦ 大像素&…

Vulnhub-IMF靶机

本篇文章旨在为网络安全渗透测试靶机教学。通过阅读本文&#xff0c;读者将能够对渗透Vulnhub系列IMF靶机有一定的了解 一、信息收集阶段 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/imf-1,162/ 因为靶机为本地部署虚拟机网段&#xff0c;查看dhcp地址池设置。得…

Linux内核中TCP协议栈的实现:tcp_close函数的深度剖析

引言 TCP(传输控制协议)作为互联网协议族中的核心协议之一,负责在不可靠的网络层之上提供可靠的、面向连接的字节流服务。Linux内核中的TCP协议栈实现了TCP协议的全部功能,包括连接建立、数据传输、流量控制、拥塞控制以及连接关闭等。本文将深入分析Linux内核中tcp_close…

java+postgresql+swagger-多表关联insert操作(七)

入参为json&#xff0c;然后根据需要对多张表进行操作&#xff1a; 入参格式&#xff1a; [{"custstoreName":"swagger-测试经销商01","customerName":"swagger-测试客户01","propertyNo":"swaggertest01",&quo…

R语言——绘制生命曲线图(细胞因子IL5)

绘制生命曲线图&#xff08;根据细胞因子&#xff09; 说明流程代码加载包读取Excel文件清理数据重命名列名处理IL-5中的"<"符号 - 替换为检测下限的一半首先找出所有包含"<"的值检查缺失移除缺失值根据IL-5中位数将患者分为高低两组 创建生存对象拟…

Python----计算机视觉处理(Opencv:道路检测完整版:透视变换,提取车道线,车道线拟合,车道线显示,)

Python----计算机视觉处理&#xff08;Opencv:道路检测之道路透视变换) Python----计算机视觉处理&#xff08;Opencv:道路检测之提取车道线&#xff09; Python----计算机视觉处理&#xff08;Opencv:道路检测之车道线拟合&#xff09; Python----计算机视觉处理&#xff0…

【Oracle篇】跨字符集迁移:基于数据泵的ZHS16GBK转AL32UTF8全流程迁移

&#x1f4ab;《博主主页》&#xff1a;奈斯DB-CSDN博客 &#x1f525;《擅长领域》&#xff1a;擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(MongoDB)有了解 &#x1f496;如果觉得文章对你有所帮…

【C++算法】50.分治_归并_翻转对

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 493. 翻转对 题目描述&#xff1a; 解法 分治 策略一&#xff1a;计算当前元素cur1后面&#xff0c;有多少元素的两倍比我cur1小&#xff08;降序&#xff09; 利用单…

深入讲解:智能合约中的读写方法

前言 在探秘区块链开发:智能合约在 DApp 中的地位及与传统开发差异一文中我提到对于智能合约中所有的写入其实都算是交易。而在一个完整的智能合约代码中最大的两个组成部分就是读取和写入。 本文将为你深入探讨该两者方法之间的区别。 写方法 写方法其实就是对区块链这一…

Go语言类型捕获及内存大小判断

代码如下&#xff1a; 类型捕获可使用&#xff1a;reflect.TypeOf()&#xff0c;fmt.Printf在的%T。 内存大小判断&#xff1a;len()&#xff0c;unsafe.Sizeof。 package mainimport ("fmt""unsafe""reflect" )func main(){var i , j 1, 2f…

MyBatis Plus 在 ZKmall开源商城持久层的优化实践

ZKmall开源商城作为基于 Spring Cloud 的高性能电商平台&#xff0c;其持久层通过 MyBatis Plus 实现了多项深度优化&#xff0c;涵盖分库分表、缓存策略、分页性能、多租户隔离等核心场景。以下是具体实践总结&#xff1a; 一、分库分表与插件集成优化 1. 分库分表策略 ​Sh…

学习MySQL第七天

夕阳无限好 只是近黄昏 一、子查询 1.1 定义 将一个查询语句嵌套到另一个查询语句内部的查询 我们通过具体示例来进行演示&#xff0c;这一篇博客更侧重于通过具体的小问题来引导大家独立思考&#xff0c;然后熟悉子查询相关的知识点 1.2 问题1 谁的工资比Tom高 方…

Nginx 常见面试题

一、nginx常见错误及处理方法 1.1 404 bad request 一般原因&#xff1a;请求的Header过大 解决办法&#xff1a; 配置nginx.conf 相关设置1. client_header_buffer_size 16k; 2. large_client_header_buffers 4 64k;1.2 413 Request Entity Too Large 一般原因&#xff1…

LeetCode 每日一题 2025/3/31-2025/4/6

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录 3/31 2278. 字母在字符串中的百分比4/1 2140. 解决智力问题4/2 2873. 有序三元组中的最大值 I4/3 2874. 有序三元组中的最大值 II4/4 1123. 最深叶节点的最近公共祖先4/5 1…

Docker Compose 常用命令 运行 docker-compose.yaml

Docker Compose 中有两个重要的概念 服务 (service)&#xff1a;一个应用的容器&#xff0c;实际上可以包括若干运行相同镜像的容器实例。 项目 (project)&#xff1a;由一组关联的应用容器组成的一个完整业务单元&#xff0c;在 docker-compose.yml 文件中定义。 为了更方便…

深度学习中的 Batch 机制:从理论到实践的全方位解析

一、Batch 的起源与核心概念 1.1 批量的中文译名解析 Batch 在深度学习领域标准翻译为"批量"或"批次"&#xff0c;指代一次性输入神经网络进行处理的样本集合。这一概念源自统计学中的批量处理思想&#xff0c;在计算机视觉先驱者Yann LeCun于1989年提出…