【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.pswp.cn/diannao/77106.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/77106.shtml
英文地址,請注明出處:http://en.pswp.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年提出…