【實戰教程】使用Spring AOP和自定義注解監控接口調用

一、背景

隨著項目的長期運行和迭代,積累的功能日益繁多,但并非所有功能都能得到用戶的頻繁使用或實際上根本無人問津。

為了提高系統性能和代碼質量,我們往往需要對那些不常用的功能進行下線處理。

那么,該下線哪些功能呢?

此時,我們就需要對接口的調用情況進行統計和分析了!

二、實戰

以下內容為主要代碼,完整代碼請參考:https://gitee.com/regexpei/daily-learning-test

以下使用 自定義注解 + AOP 的方式,對接口調用進行記錄。

1. 創建項目,添加依賴

<dependencies>  <!-- 提供自動配置、日志、YAML等核心功能 -->  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter</artifactId>  </dependency>  <!-- 提供面向切面編程支持 -->  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-aop</artifactId>  </dependency>  <!-- 用于構建Web,包括RESTful和基于Servlet的Web應用,包含了Spring MVC、Tomcat等 -->  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId>  </dependency>  <!-- 通過注解減少樣板代碼的Java庫,自動生成getter、setter等方法 -->  <dependency>  <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId>  <optional>true</optional>  </dependency>  <!-- Swagger的注解庫,允許開發者為API添加文檔和元數據 -->  <dependency>  <groupId>io.swagger</groupId>  <artifactId>swagger-annotations</artifactId>  <version>1.6.2</version>  </dependency>  <!-- 用于Java對象的JSON序列化/反序列化的庫,Fastjson的繼任者 -->  <dependency>  <groupId>com.alibaba.fastjson2</groupId>  <artifactId>fastjson2</artifactId>  <version>2.0.41</version>  </dependency>  <!-- 為Spring Boot應用提供了測試所需的依賴項,包括JUnit等,但僅限于測試階段 -->  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope>  <exclusions>  <!-- 排除已包含的SLF4J API版本,避免版本沖突 -->  <exclusion>  <groupId>org.slf4j</groupId>  <artifactId>slf4j-api</artifactId>  </exclusion>  </exclusions>  </dependency>  <!-- Java工具包,提供了許多實用的工具類和方法 -->  <dependency>  <groupId>cn.hutool</groupId>  <artifactId>hutool-all</artifactId>  <version>5.8.25</version>  </dependency>  
</dependencies>

2. 自定義注解和實體類

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface ApiOprLogAnno {@ApiModelProperty(value = "接口類型")String apiType() default "";@ApiModelProperty(value = "接口說明")String apiDetail() default "";@ApiModelProperty(value = "是否保存請求參數")boolean isSaveRequest() default false;@ApiModelProperty(value = "是否保存響應結果")boolean isSaveResponse() default false;}
@Setter
@Getter
public class ApiOprLog {@ApiModelProperty(name = "主鍵")private String id;@ApiModelProperty(name = "源IP")private String sourceIp;@ApiModelProperty(name = "用戶名")private String username;@ApiModelProperty(name = "方法")private String method;@ApiModelProperty(name = "請求參數")private String reqParams;@ApiModelProperty(name = "響應結果")private String resResult;@ApiModelProperty(name = "異常信息")private String exMessage;@ApiModelProperty(name = "異常詳細")private String exJson;@ApiModelProperty(name = "接口模塊")private String apiModule;@ApiModelProperty(name = "接口類型")private String apiType;@ApiModelProperty(name = "接口說明")private String apiDetail;@ApiModelProperty(name = "創建時間")private Date createTime;@ApiModelProperty(name = "更新時間")private Date updateTime;
}

3. 創建切面類

@Slf4j
@Aspect
@Component
public class ApiOprAspect {@Value("${spring.application.name}")private String moduleName;/*** 從請求中獲取 IP** @return IP*/private static String getIpFromRequest() {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes != null) {HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);return IpUtil.getRealIp(request);}return Constants.UNKNOWN;}@Pointcut("@annotation(cn.regexp.dailylearningtest.anno.ApiOprLogAnno)")public void pointcut() {}@Around("pointcut()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {String id = IdUtil.fastSimpleUUID();Object result;try {// 執行方法前操作executeBefore(proceedingJoinPoint, id);result = proceedingJoinPoint.proceed();// 執行方法后操作executeAfter(proceedingJoinPoint, id, result);} catch (Throwable ex) {// 執行方法異常后操作executeAfterEx(ex, id);throw ex;}return result;}private void executeBefore(ProceedingJoinPoint proceedingJoinPoint, String id) {// 獲取目標方法的簽名信息MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();// 從方法簽名中獲取 ApiOprLogAnno 注解的信息ApiOprLogAnno apiOprLogAnno = signature.getMethod().getAnnotation(ApiOprLogAnno.class);// 封裝 ApiOprLog 對象ApiOprLog apiOprLog = packaging(id, getIpFromRequest(), signature.toString(), apiOprLogAnno);if (apiOprLogAnno.isSaveRequest()) {// 保存請求參數// 獲取方法簽名的參數名數組String[] parameterNames = signature.getParameterNames();// 獲取連接點傳遞的實參數組 Object[] args = proceedingJoinPoint.getArgs();Map<String, Object> paramMap = new HashMap<>(parameterNames.length);for (int i = 0; i < parameterNames.length; i++) {if (!RequestAttributes.REFERENCE_REQUEST.equals(parameterNames[i])) {paramMap.put(parameterNames[i], args[i]);}}apiOprLog.setReqParams(JSON.toJSONString(paramMap));}// 入庫操作log.debug("executeBefore apiOprLog: {}", JSON.toJSONString(apiOprLog));}private void executeAfter(ProceedingJoinPoint proceedingJoinPoint, String id, Object result) {// 獲取目標方法的簽名信息MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();// 從方法簽名中獲取 ApiOprLogAnno 注解的信息ApiOprLogAnno apiOprLogAnno = signature.getMethod().getAnnotation(ApiOprLogAnno.class);if (!apiOprLogAnno.isSaveResponse()) {return;}ApiOprLog apiOprLog = new ApiOprLog();apiOprLog.setId(id);apiOprLog.setResResult(JSON.toJSONString(result));apiOprLog.setUpdateTime(DateTime.now());// 入庫操作log.debug("executeAfter apiOprLog: {}", JSON.toJSONString(apiOprLog));}private void executeAfterEx(Throwable ex, String id) {ApiOprLog apiOprLog = new ApiOprLog();apiOprLog.setId(id);apiOprLog.setExMessage(ex.toString());apiOprLog.setExJson(ExceptionUtil.stacktraceToString(ex));apiOprLog.setUpdateTime(DateTime.now());// 入庫操作log.debug("executeAfterEx apiOprLog: {}", JSON.toJSONString(apiOprLog));}/*** 封裝 ApiOprLog** @param id            主鍵* @param sourceIp      IP* @param method        方法* @param apiOprLogAnno 注解* @return 接口操作日志對象*/private ApiOprLog packaging(String id,String sourceIp,String method,ApiOprLogAnno apiOprLogAnno) {ApiOprLog apiOprLog = new ApiOprLog();apiOprLog.setId(id);apiOprLog.setSourceIp(sourceIp);apiOprLog.setUsername("Regexp");apiOprLog.setMethod(method);apiOprLog.setApiModule(moduleName);apiOprLog.setApiType(apiOprLogAnno.apiType());apiOprLog.setApiDetail(apiOprLogAnno.apiDetail());apiOprLog.setCreateTime(DateTime.now());return apiOprLog;}
}

4. 進行測試

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/get")@ApiOprLogAnno(apiType = "查詢", apiDetail = "查詢單個用戶", isSaveResponse = true)public Person get() {return new Person("Regexp", 18);}@PostMapping("/save")@ApiOprLogAnno(apiType = "保存", apiDetail = "保存單個用戶", isSaveRequest = true)public String save(@RequestBody Person person) {log.debug("save person: {}", JSON.toJSONString(person));return "ok";}@GetMapping("/getEx")@ApiOprLogAnno(apiType = "查詢", apiDetail = "查詢單個用戶(異常情況)")public Person getEx() {throw new IllegalArgumentException();}
}

三、問題記錄

1. 引用不是注解類型
描述

啟動項目時,報錯如下:

Caused by: java.lang.IllegalArgumentException: error Type referred to is not an annotation type: cn$regexp$dailylearningtest$anno$ApiOprLog

在這里插入圖片描述

分析

從報錯信息來看,顯示為:錯誤的類型,引用的不是一個注解類型。
Ctrl + Shift + F 全局搜索 ApiOprLog,看看哪些地方有用到 ApiOprLog。
經過搜索,發現在@annotation中引用了 ApiOprLog(注解重命名后,這里忘記改了),但 ApiOprLog 并不是注解類型,所以導致啟動項目時,Spring找到了這個類但這個類卻不是注解,就報了這個錯。

@Pointcut("@annotation(cn.regexp.dailylearningtest.anno.ApiOprLog)")
public void pointcut(){}

將 ApiOprLog 修改為正確的注解名稱即可。

@Pointcut("@annotation(cn.regexp.dailylearningtest.anno.ApiOprLogAnno)")
public void pointcut(){}
2. 依賴沖突
描述

SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found
binding in
[jar:file:/D:/OpenSource/maven-repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in
[jar:file:/D:/OpenSource/maven-repository/org/slf4j/slf4j-reload4j/1.7.36/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an
explanation. SLF4J: Actual binding is of type
[ch.qos.logback.classic.util.ContextSelectorStaticBinder]

在這里插入圖片描述

分析

從以上信息來看,應該是發生了依賴沖突導致的。
在控制臺輸入 mvn dependency:tree 查看項目中所有使用的依賴以及依賴中引用的依賴,查找哪些依賴使用了 slf4j,在其中一個依賴中使用exclusions進行排除即可,如下:

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></exclusion></exclusions></dependency>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/13495.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/13495.shtml
英文地址,請注明出處:http://en.pswp.cn/web/13495.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

貪心算法: 單調遞增的數字

參考資料&#xff1a;代碼隨想錄 題目鏈接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 倒序遍歷每個數字&#xff0c;遇到前一個比后一個大的就減一&#xff0c;最后統一把后面幾位置為9 String str n"";char[] chars str.toCharArray();int flag c…

docker部署kafka實戰

目錄 一、部署kafaka、zookeeper 二、測試信息發送與接收 三、kafka進階 一、部署kafaka、zookeeper 請提前安裝docker、docker-compose 安裝docker&#xff1a;docker--安裝docker-ce-CSDN博客 安裝docker-compose&#xff1a; 安裝docker-compose_安裝 docker-compose-CSD…

云下到云上,麗迅物流如何實現數據庫降本50% | OceanBase案例

在2024年3月20日的首場OceanBase數據庫城市行活動中&#xff0c;專注于物流及供應鏈解決方案的麗迅物流的架構師陽磊&#xff0c;圍繞“OB Cloud在麗迅物流的實踐”這一主題&#xff0c;進行了精彩的演講。本文為此次演講的內容回顧。 在麗迅物流&#xff08;Lesoon Logistics…

小demo - 列表hide or not (含代碼)

直接上代碼 <!DOCTYPE html> <html><head><style>.menu {width: 220px;height: 800px;border: 1px solid #dddddd;}.item {cursor: pointer;}.menu .header {padding: 10px 5px;background-color: goldenrod;}.menu .content a {display: block;paddi…

線程安全-1 synchronized鎖升級

一.說一下synchronized關鍵字的底層原理 1.synchronized又叫同步鎖&#xff0c;采用互斥的方式使同一時刻只能有一個線程持有鎖。 2.jdk1.6及以前&#xff0c;synchronized底層是用monitor實現的。monitor是jvm級別的對象&#xff0c;由c實現。每一個對象對應一個monitor&…

9.1 Go語言入門(環境篇)

Go語言入門&#xff08;環境篇&#xff09; 目錄一、什么是Go語言二、下載安裝配置Go語言開發環境1. 下載2. 安裝3. 配置環境變量4. 安裝環境驗證 三、 開發工具1. 下載2. 安裝3. 激活4. 配置SDK 四、 創建go工程文件并運行1. 創建go工程2. 示例代碼3. 運行代碼 目錄 一、什么…

軟件開源協議與QT的開源協議介紹

一.常見的六種開源協議 1.BSD協議 BSD協議全稱為“Berkely Software Distribution”&#xff0c;中文譯為“伯克利軟件發行版”。其最早用于伯克利UNIX操作系統上的開源貢獻。 主要特點&#xff1a; 允許修改源碼 允許源碼再發布 允許商業軟件發布和銷售 約束&#xff1…

shell 腳本筆記2

3.env與set區別 env用于查看系統環境變量 set用于查看系統環境變量自定義變量函數 4.常用環境變量 變量名稱含義PATH命令搜索的目錄路徑, 與windows的環境變量PATH功能一樣LANG查詢系統的字符集HISTFILE查詢當前用戶執行命令的歷史列表 Shell變量&#xff1a;自定義變量 目標…

HCIP【VRRP、MSTP、VLAN綜合實驗】

目錄 一、實驗拓撲圖&#xff1a; ?編輯二、實驗要求 三、實驗思路 四、實驗步驟 &#xff08;1&#xff09; eth-trunk技術配置 &#xff08;2&#xff09;vlan 技術配置 &#xff08;3&#xff09;配置SW1、SW2、AR1、ISP的IP地址 &#xff08;4&#xff09;在交換機…

FBB-Frontiers in Bioengineering and Biotechnology

文章目錄 一、期刊簡介二、征稿信息三、期刊表現四、投稿須知五、投稿咨詢 一、期刊簡介 Frontiers in Bioengineering and Biotechnology是專注生物工程和生物技術領域的開放獲取期刊。 研究范圍涵蓋生物材料、生物力學、生物工藝工程、生物安全和生物安保&#xff0c;生物傳…

QT項目-歡樂斗地主游戲

QT項目-歡樂斗地主游戲 游戲概述游戲規則牌型牌型的大小游戲角色游戲規則游戲的勝負游戲計分規則 游戲相關的類介紹卡牌類玩家類窗口類游戲控制類游戲策略類線程類音頻類 游戲主要組件卡牌玩家窗口 游戲控制源碼 游戲概述 游戲規則 不同地域游戲規則可能有些許差異&#xff0c…

MySQL之Schema與數據類型優化(三)

Schema與數據類型優化 BLOB和TEXT類型 BLOB和TEXT都是為存儲很大的數據而設計的字符串數據類型&#xff0c;分別采用二進制和字符方式存儲。 實際上它們分別屬于兩組不同的數據類型家族:字符類型是TINYTEXT&#xff0c;SMALLTEXT,TEXT&#xff0c;MEDIUMTEXT&#xff0c;LONG…

Spring Cloud整合Sentinel

1、引入依賴 鏈接: 點擊查看依賴關系 父pom <spring.cloud.version>Hoxton.SR12</spring.cloud.version> <spring.cloud.alibaba.version>2.2.10-RC1</spring.cloud.alibaba.version>Sentinel應用直接引用starter <dependency><groupId&…

【UE5.1】* 動畫重定向 (讓你的角色可以使用小白人全部動畫)

前言 這里以小白人動畫重定向給商城資產“Adventure Character”中的角色為例&#xff0c;闡述如何使用UE5.1進行動畫重定向。 步驟 1. 創建一個IK綁定 這里選擇小白人的骨骼網格體 這里命名為“IKRig_Mannequin” 2. 再新建一個IK綁定&#xff0c;這里使用你要替換給的角色…

MyBatis入門——MyBatis XML配置文件(3)

目錄 一、配置連接字符串和MyBatis 二、寫持久層代碼 1、添加 mapper 接口 2、添加 USerInfoXmlMapper.xml 3、測試類代碼 三、增刪改查操作 1、增&#xff08;Insert&#xff09; 返回自增 id 2、刪&#xff08;Delete&#xff09; 3、改&#xff08;update&#xf…

軟考--試題六--中介者模式(Mediator)

中介者模式(Meditor) 意圖 用一個中介對象來封裝一系列的對象交互。中介者使各對象不需要顯式地相互引用&#xff0c;從而使其耦合松散&#xff0c;而且可以獨立地改變它們之間的交互 結構 適用性 1、一組對象以定義良好但是復雜的方式進行通信&#xff0c;產生的相互依賴關…

民國漫畫雜志《時代漫畫》第17期.PDF

時代漫畫17.PDF: https://url03.ctfile.com/f/1779803-1248612629-85326d?p9586 (訪問密碼: 9586) 《時代漫畫》的雜志在1934年誕生了&#xff0c;截止1937年6月戰爭來臨被迫停刊共發行了39期。 ps:資源來源網絡&#xff01;

力扣HOT100 - 1143. 最長公共子序列

解題思路&#xff1a; 動態規劃 class Solution {public int longestCommonSubsequence(String text1, String text2) {int m text1.length(), n text2.length();int[][] dp new int[m 1][n 1];for (int i 1; i < m; i) {char c1 text1.charAt(i - 1);for (int j 1…

深度學習之基于YoloV5的動物識別系統

歡迎大家點贊、收藏、關注、評論啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代碼。 文章目錄 一項目簡介 二、功能三、系統四. 總結 一項目簡介 一、項目背景與目標 在生態研究、動物保護、以及畜牧業等多個領域&#xff0c;對動物進行準確、高效的識別都具有重…

形態學操作:腐蝕、膨脹、開閉運算、頂帽底帽變換、形態學梯度區別與聯系

一、總述相關概念 二、相關問題 1.形態學操作中的腐蝕和膨脹對圖像有哪些影響&#xff1f; 形態學操作中的腐蝕和膨脹是兩種常見的圖像處理技術&#xff0c;它們通過對圖像進行局部區域的像素值替換來實現對圖像形狀的修改。 腐蝕操作通常用于去除圖像中的噪聲和細小的細節&a…