【实战教程】使用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.rhkb.cn/news/329398.html

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

相关文章

QTextEdit 控件上显示信息:

目录 1. 使用 append 方法: 2. 使用 setPlainText 方法 3.例子&#xff1a; 1. 使用 append 方法: 如果你希望在 QTextEdit 控件上追加显示新的信息&#xff0c;可以使用 append 方法。例如&#xff0c;当你想要追加一行新的日志信息&#xff1a; self.text_edit.append(&…

卷积神经网络(CNN)详细介绍及其原理详解

卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称CNN&#xff09;是深度学习中非常重要的一类神经网络&#xff0c;主要用于图像识别、图像分类、物体检测等计算机视觉任务。本文将详细介绍卷积神经网络的基本概念、结构组成及其工作原理&#xff0c;并…

leetcode以及牛客网单链表相关的题、移除链表元素、链表的中间节点、合并两个有序链表、反转链表、链表分割、倒数第k个节点等的介绍

文章目录 前言一、移除链表元素二、链表的中间节点三、合并两个有序链表四、反转链表五、链表分割六、倒数第k个节点总结 前言 leetcode以及牛客网单链表相关的题、移除链表元素、链表的中间节点、合并两个有序链表、反转链表、链表分割、倒数第k个节点等的介绍 一、移除链表元…

集群分发脚本xsync

1.环境准备 1.准备三台服务器&#xff08;我这里使用虚拟机,操作系统 CentOS7 &#xff09;它们的IP分别为 192.168.188.135、192.168.188.136、192.168.188.137 2.先将三台机器的主机名修改&#xff0c;为每台主机设置hostname&#xff08;具体名称由自己定义&#xff09;&am…

https为何安全?

HTTPS&#xff08;超文本传输安全协议&#xff09;是一种用于安全通信的网络协议&#xff0c;它在HTTP协议的基础上通过SSL/TLS&#xff08;安全套接层/传输层安全&#xff09;协议来加密数据&#xff0c;以保护网络数据的传输安全。 TLS/SSL 基础概念 概念源自百度百科&…

电磁兼容(EMC):时钟电路PCB设计

目录 1. 布局 2. 布线 时钟电路做为产品内部的强辐射源&#xff0c;在设计阶段已经选用展频或者分频方案后&#xff0c;见另外接下来就需要对PCB的耦合路径进行规划设计。时钟电路具体的PCB设计具体要求如下&#xff1a; 1. 布局 结构干涉&#xff1a;时钟电路的晶振和法拉电…

BUUCTF---misc---我吃三明治

1、下载附件是一张图片 2、在winhex分析&#xff0c;看到一串整齐的编码有点可疑&#xff0c;保存下来&#xff0c;拿去解码&#xff0c;发现解不了&#xff0c;看来思路不对 3、再仔细往下看的时候也发现了一处这样的编码&#xff0c;但是这次编码后面多了一段base编码 4、拿去…

JS对象超细

目录 一、对象是什么 1.对象声明语法 2.对象有属性和方法组成 二、对象的使用 1.对象的使用 &#xff08;1&#xff09;查 &#xff08;2&#xff09;改 &#xff08;3&#xff09;增 &#xff08;4&#xff09;删&#xff08;了解&#xff09; &#xff08;5&#xf…

Linux文件:缓冲区、缓冲区刷新机制 | C库模拟实现

Linux文件&#xff1a;缓冲区、缓冲区刷新机制 | C库模拟实现 一、缓冲区的作用二、缓冲区的刷新机制三、测试样例解析3.1 测试样例和运行结果3.2 结果分析1、向显示器文件写入&#xff1a;2、向磁盘文件进行写入&#xff1a; 四、语言级别的缓冲区究竟在哪&#xff1f;五、C库…

网络原理3

运营商路由器&#xff0c;也可以把它当做一个NAT设备它就会对中间经过的数据包&#xff0c;进行网络地址转换当内网设备经过运营商路由器访问外网的时候就会把IP数据包中的源ip&#xff0c;替换成它自己的ip. 我的电脑要发送一个数据给cctalk服务器此时&#xff0c;我的电脑上就…

二叉树求解大小操作详解

目录 一、求所有结点个数 1.1 递归思路 1.2 递归分支图 1.3 递归栈帧图 1.4 C语言实现 二、求叶子结点个数 2.1 递归思路 2.2 递归分支图 2.3 递归栈帧图 2.4 C语言实现 三、求第K层的结点个数 3.1 递归思路 3.2 递归分支图 3.3 递归栈帧图 3.4 C语言实现 四、求…

高性能负载均衡的分类及架构分析

如何选择与部署适合的高性能负载均衡方案&#xff1f; 当单服务器性能无法满足需求&#xff0c;高性能集群便成为提升系统处理能力的关键。其核心在于通过增加服务器数量&#xff0c;强化整体计算能力。而集群设计的挑战在于任务分配&#xff0c;因为无论在哪台服务器上执行&am…

新火种AI|净利润上升628%,英伟达财报说明AI热潮还将持续

作者&#xff1a;一号 编辑&#xff1a;美美 AI大潮仍未放缓&#xff0c;英伟达再次超越预期。 今天凌晨&#xff0c;全球AI算力芯片龙头&#xff0c;被称为“AI时代卖铲人”的英伟达&#xff0c;正式公布了截至2024年4月28日的2025财年第一财季财报&#xff0c;其中第一财季…

java8总结

java8总结 java8新特性总结1. 行为参数化2. lambda表达式2.1 函数式接口2.2 函数描述符 3. Stream API3.1 付诸实践 java8新特性总结 行为参数化lambda表达式Stream Api 1. 行为参数化 定义&#xff1a;行为参数化&#xff0c;就是一个方法接受多个不同的行为作为参数&#x…

C++第三方库【JSON】— jsoncpp

目录 认识JSON jsoncpp库 安装&使用 认识jsoncpp Json::Value jsoncpp序列化 jsoncpp反序列化 认识JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式&#xff0c;采用完全独立于编程语言的文本格式来存储和表示数据&#xff0c;常用于在客户端和服…

钉钉网页应用使用JSAPI报错,dd.alert提示errorCode:3.errorMessage:No value for message

问题分析&#xff1a; 起因是我用下图这个页面&#xff08;配置JSAPI鉴权&#xff09;的链接下载了JSAPI&#xff08;客户端API&#xff09;的SDK&#xff0c;但其实如图所示这个版本是2.10.3&#xff1a; 通过查看dingtalk-jsapi的npm版本&#xff0c;可以知道钉钉的JSAPI已…

c++设计模式-->访问者模式

#include <iostream> #include <string> #include <memory> using namespace std;class AbstractMember; // 前向声明// 行为基类 class AbstractAction { public:virtual void maleDoing(AbstractMember* member) 0;virtual void femaleDoing(AbstractMemb…

荣耀MagicBook X 14 Pro锐龙版 2023 集显(FRI-H76)笔记本电脑原装出厂Windows11系统工厂模式安装包下载,带F10智能还原

恢复开箱状态预装OEM系统&#xff0c;适用型号&#xff1a;HONOR荣耀FRI-H76、FRI-H56 链接&#xff1a;https://pan.baidu.com/s/1Lcg45byotu5kDDSBs3FStA?pwdl30r 提取码&#xff1a;l30r 华为荣耀原装WIN11系统工厂安装包&#xff0c;含F10一键恢复功能、系统自带所有驱…

H800基础能力测试

H800基础能力测试 参考链接A100、A800、H100、H800差异H100详细规格H100 TensorCore FP16 理论算力计算公式锁频安装依赖pytorch FP16算力测试cublas FP16算力测试运行cuda-samples 本文记录了H800基础测试步骤及测试结果 参考链接 NVIDIA H100 Tensor Core GPU Architecture…

快速搭建SpringMvc项目

一、什么是springMvc 1、介绍 Spring Web MVC是基于Servlet API构建的原始Web框架&#xff0c;从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称&#xff08; spring-webmvc &#xff09;&#xff0c;但它通常被称为“Spring MVC”。 在控制…