SpringBoot框架使用AOP + 自定义注解实现请求日志记录

一、SpringBoot记录日志

文章目录

    • 一、SpringBoot记录日志
      • 1.1、环境搭建
      • 1.2、配置FastJson
      • 1.3、自定义LogRecord注解
      • 1.4、定义日志实体类
      • 1.5、创建HttpRequestUtil工具类
      • 1.6、定义AOP切面
      • 1.7、编写测试类
      • 1.8、运行测试

1.1、环境搭建

  • 搭建SpringBoot工程。
  • 引入【spring-boot-starter-parent】依赖。
  • 引入【spring-boot-starter-web】依赖。
  • 引入【spring-boot-starter-aop】依赖。
  • 引入【fastjson】依赖。
<!-- 引入 SpringBoot 父工程依赖 -->
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.0.RELEASE</version>
</parent><!-- 引入 web 依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!-- 排除 jackson 依赖 --><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId></exclusion></exclusions>
</dependency>
<!-- 引入 aop 依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 引入 fastjson 依赖 -->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.77</version>
</dependency>

1.2、配置FastJson

package com.spring.boot.demo.config;import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;/*** @author Js* @version 1.0.0* @Date: 2023/11/02 12:47* @Description FastJson 配置类*/
@Configuration
public class CustomFastJsonConfig {@Beanpublic HttpMessageConverters fastjsonHttpMessageConverters() {// 创建 FastJsonHttpMessageConverter 消息转换器对象FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();// 创建 FastJsonConfig 配置类对象FastJsonConfig fastJsonConfig = new FastJsonConfig();// 设置编码字符集fastJsonConfig.setCharset(StandardCharsets.UTF_8);// 设置日期格式fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");// 设置序列化特征: SerializerFeature 是一个枚举,可以选择不同的序列化特征SerializerFeature[] serializerFeatures = new SerializerFeature[] {// WriteNullStringAsEmpty: 如果字符串等于 null,那么会被序列化成空字符串 ""SerializerFeature.WriteNullStringAsEmpty,// WriteNullNumberAsZero: 如果数字等于 null,那么会被序列化成 0SerializerFeature.WriteNullNumberAsZero,// WriteNullBooleanAsFalse: 如果布尔类型等于 null,那么会被序列化成 falseSerializerFeature.WriteNullBooleanAsFalse,// PrettyFormat: 美化JSONSerializerFeature.PrettyFormat};fastJsonConfig.setSerializerFeatures(serializerFeatures);// 配置添加到消息转换器里面fastJsonHttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8);fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);// 设置响应JSON格式数据List<MediaType> mediaTypeList = new ArrayList<>();mediaTypeList.add(MediaType.APPLICATION_JSON); // JSON 格式数据// 设置消息转换器支持的格式fastJsonHttpMessageConverter.setSupportedMediaTypes(mediaTypeList);// 返回消息转换器return new HttpMessageConverters(fastJsonHttpMessageConverter);}}

1.3、自定义LogRecord注解

  • 这里我们自定义一个@LogRecord注解,该注解使用在方法上面,用于标记AOP切面会拦截这个方法,并且记录请求日志信息。
package com.spring.boot.demo.anno;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author Js* @version 1.0.0* @Date: 2023/11/02 12:47* @Description 自定义日志注解*/
// 注解可以保留到运行期间
@Retention(RetentionPolicy.RUNTIME)
// 注解使用在方法上面
@Target(ElementType.METHOD)
public @interface LogRecord {/*** 操作名称*/String opName();/*** 描述信息*/String desc() default "";
}

1.4、定义日志实体类

为了能够收集请求日志的信息,这里定义一个日志实体类来保存每一次请求的日志信息。

package com.spring.boot.demo.pojo;import java.io.Serializable;/*** @author Js* @version 1.0.0* @Date: 2023/11/2 22:45* @Description 日志实体类*/
public class LogRecordEntity implements Serializable {/** 日志唯一标识 */private String id;/** 操作名称 */private String opName;/** 请求路径 */private String path;/** 请求方式 */private String method;/** 请求IP地址 */private String requestIp;/** 全限定类名称 */private String qualifiedName;/** 请求入参 */private String inputParam;/** 请求出参 */private String outputParam;/** 异常信息 */private String errorMsg;/** 请求开始时间 */private String requestTime;/** 请求响应时间 */private String responseTime;/** 接口耗时,单位:ms */private String costTime;/** 请求是否成功 */private String status;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getOpName() {return opName;}public void setOpName(String opName) {this.opName = opName;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}public String getRequestIp() {return requestIp;}public void setRequestIp(String requestIp) {this.requestIp = requestIp;}public String getQualifiedName() {return qualifiedName;}public void setQualifiedName(String qualifiedName) {this.qualifiedName = qualifiedName;}public String getInputParam() {return inputParam;}public void setInputParam(String inputParam) {this.inputParam = inputParam;}public String getOutputParam() {return outputParam;}public void setOutputParam(String outputParam) {this.outputParam = outputParam;}public String getErrorMsg() {return errorMsg;}public void setErrorMsg(String errorMsg) {this.errorMsg = errorMsg;}public String getRequestTime() {return requestTime;}public void setRequestTime(String requestTime) {this.requestTime = requestTime;}public String getResponseTime() {return responseTime;}public void setResponseTime(String responseTime) {this.responseTime = responseTime;}public String getCostTime() {return costTime;}public void setCostTime(String costTime) {this.costTime = costTime;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}@Overridepublic String toString() {return "LogRecordEntity{" +"id='" + id + '\'' +", opName='" + opName + '\'' +", path='" + path + '\'' +", method='" + method + '\'' +", requestIp='" + requestIp + '\'' +", qualifiedName='" + qualifiedName + '\'' +", inputParam='" + inputParam + '\'' +", outputParam='" + outputParam + '\'' +", errorMsg='" + errorMsg + '\'' +", requestTime='" + requestTime + '\'' +", responseTime='" + responseTime + '\'' +", costTime='" + costTime + '\'' +", status='" + status + '\'' +'}';}
}

1.5、创建HttpRequestUtil工具类

  • 创建一个获取HTTP请求和响应对象的工具类,在SpringBoot框架中,可以通过RequestContextHolder类获取到HTTP请求属性对象,通过该对象可以获取到Request、Response对象。
package com.spring.boot.demo.util;import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author Js* @version 1.0.0* @Date: 2023/11/2 23:03* @Description HTTP请求的工具类,用于获取Request、Response相关信息*/
public final class HttpRequestUtil {/*** 从 SpringBoot 中获取 Request 请求对象* @return 返回当前请求的 Request 对象*/public static HttpServletRequest getRequest() {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes == null) {return null;}ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;return attributes.getRequest();}/*** 从 SpringBoot 中获取 Response 请求对象* @return 返回当前请求的 Response 对象*/public static HttpServletResponse getResponse() {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes == null) {return null;}ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;return attributes.getResponse();}}

1.6、定义AOP切面

package com.spring.boot.demo.config;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.spring.boot.demo.anno.LogRecord;
import com.spring.boot.demo.pojo.LogRecordEntity;
import com.spring.boot.demo.util.HttpRequestUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.UUID;/*** @author Js* @version 1.0.0* @Date: 2023/11/2 12:52* @Description 自定义日志切面*/
// 标记当前类是一个切面类
@Aspect
// 将当前类放入IOC容器
@Component
public class LogAspect {/*** 创建线程局部变量*/private ThreadLocal<LogRecordEntity> threadLocal = new ThreadLocal<>();/*** 定义切入点,这里我们使用AOP切入自定义【@LogRecord】注解的方法*/@Pointcut("@annotation(com.spring.boot.demo.anno.LogRecord)")public void pointCut() {}/*** 前置通知,【执行Controller方法之前】执行该通知方法*/@Before("pointCut()")public void beforeAdvice() {System.out.println("前置通知......"); // TODO delete}/*** 后置通知,【Controller方法执行完成,返回方法的返回值之前】执行该通知方法*/@After("pointCut()")public void afterAdvice() {System.out.println("后置通知......"); // TODO delete}/*** 环绕通知,执行Controller方法的前后执行* @param joinPoint 连接点*/@Around("pointCut()")public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {// 请求开始时间String requestTime = String.valueOf(System.currentTimeMillis());System.out.println("环绕通知之前....."); // TODO delete// 获取当前请求对象HttpServletRequest request = HttpRequestUtil.getRequest();if (request == null) {return null;}// 获取请求相关信息LogRecordEntity entity = new LogRecordEntity();entity.setId(UUID.randomUUID().toString().replace("-", ""));entity.setPath(request.getRequestURI());entity.setMethod(request.getMethod());entity.setRequestIp(request.getRemoteHost());entity.setRequestTime(requestTime);// 反射获取调用方法MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();if (method.isAnnotationPresent(LogRecord.class)) {// 获取注解信息LogRecord annotation = method.getAnnotation(LogRecord.class);entity.setOpName(annotation.opName());}// 获取全限定类名称String name = method.getName();// 获取请求参数String inputParam = JSONObject.toJSONString(joinPoint.getArgs());entity.setInputParam(inputParam);// 设置局部变量threadLocal.set(entity);// 调用Controller方法Object ret = joinPoint.proceed();System.out.println("环绕通知之后....."); // TODO deletereturn ret;}/*** 返回值通知,Controller执行完成之后,返回方法的返回值时候执行* @param ret 返回值的名称*/@AfterReturning(pointcut = "pointCut()", returning = "ret")public Object afterReturning(Object ret) {System.out.println("返回值通知......ret=" + ret); // TODO delete// 获取日志实体对象LogRecordEntity entity = this.getEntity();String outputParam = JSON.toJSONString(ret);entity.setOutputParam(outputParam); // 保存响应参数entity.setStatus("成功"); // 设置成功标识// TODO 这里就可以做一些持久胡操作,例如:保存日志到数据表里面// 一定要删除 ThreadLocal 变量threadLocal.remove();System.out.println(entity); // TODO deletereturn ret;}/*** 异常通知,当Controller方法执行过程中出现异常时候,执行该通知* @param ex 异常名称*/@AfterThrowing(pointcut = "pointCut()", throwing = "ex")public void throwingAdvice(Throwable ex) {System.out.println("异常通知......"); // TODO delete// 获取日志实体对象LogRecordEntity entity = this.getEntity();StringWriter errorMsg = new StringWriter();ex.printStackTrace(new PrintWriter(errorMsg, true));entity.setErrorMsg(errorMsg.toString()); // 保存响应参数entity.setStatus("失败"); // 设置成功标识// TODO 这里就可以做一些持久胡操作,例如:保存日志到数据表里面// 一定要删除 ThreadLocal 变量threadLocal.remove();System.out.println(entity); // TODO delete}/****************************************************/private LogRecordEntity getEntity() {// 获取局部变量LogRecordEntity entity = threadLocal.get();long start = Long.parseLong(entity.getRequestTime());long end = System.currentTimeMillis();// 获取响应时间、耗时entity.setCostTime((end - start) + "ms");entity.setResponseTime(String.valueOf(end));return entity;}
}

1.7、编写测试类

package com.spring.boot.demo.controller;import com.spring.boot.demo.anno.LogRecord;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author Js* @version 1.0.0* @Date: 2023/11/2 22:58* @Description*/
@RestController
@RequestMapping("/api/aop")
public class LogController {@LogRecord(opName = "测试日志", desc = "测试日志描述内容")@GetMapping("/log")public String demo() {System.out.println("开始执行业务逻辑代码......");return "success.";}@LogRecord(opName = "测试日志", desc = "测试日志描述内容")@GetMapping("/error")public String error() {System.out.println("开始执行业务逻辑代码......");int i = 10 / 0;return "success.";}}

1.8、运行测试

启动工程,浏览器分别访问两个地址【http://127.0.0.1:8081/api/aop/log】和【http://127.0.0.1:8081/api/aop/error】,查看控制台日志输出。

在这里插入图片描述

到此,SpringBoot利用AOP和自定义注解实现日志记录就成功啦;目前是直接在控制台打印,也可以将其信息保存到数据库中;

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

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

相关文章

K8S的pod创建过程

创建流程图 用户发起请求创建deployment&#xff1b;apiserver收到创建资源的请求&#xff0c;apiserver对客户端操作进行身份认证&#xff0c;认证完成后接受该请求&#xff0c;并把相关的信息保存到etcd中&#xff0c;然后返回确认信息给客户端&#xff1b;apiserver向etcd…

【JVM】双亲委派机制、打破双亲委派机制

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 JVM 一、双亲委派机制1.1 双亲委派的作用1.…

Excel文档名称批量翻译的高效方法

在处理大量文件时&#xff0c;我们常常需要借助一些工具来提高工作效率。例如&#xff0c;在需要对Excel文档名称进行批量翻译时&#xff0c;一个方便快捷的工具可以帮助我们省去很多麻烦。今天&#xff0c;我将介绍一款名为固乔文件管家的软件&#xff0c;它能够帮助我们轻松实…

基于java+springboot+vue在线选课系统

项目介绍 本系统结合计算机系统的结构、概念、模型、原理、方法&#xff0c;在计算机各种优势的情况下&#xff0c;采用JAVA语言&#xff0c;结合SpringBoot框架与Vue框架以及MYSQL数据库设计并实现的。员工管理系统主要包括个人中心、课程管理、专业管理、院系信息管理、学生…

TrafficWatch 数据包嗅探器工具

TrafficWatch 是一种数据包嗅探器工具&#xff0c;允许您监视和分析 PCAP 文件中的网络流量。它提供了对各种网络协议的深入了解&#xff0c;并可以帮助进行网络故障排除、安全分析等。 针对 ARP、ICMP、TCP、UDP、DNS、DHCP、HTTP、SNMP、LLMNR 和 NetBIOS 的特定于协议的数据…

【Unity实战】最全面的库存系统(五)

文章目录 先来看看最终效果前言配置商店系统数据创建另一个NPC绘制商店UI控制商店开关列出商品添加和删除物品功能添加商品到购物车购买商品购物车删除物品商店预览效果购买和出售切换出售功能保存商店数据快捷栏物品切换和使用完结 先来看看最终效果 前言 本期也是最好一期&a…

直播间讨论区需要WebSocket,简单了解下

由于 http 存在一个明显的弊端&#xff08;消息只能有客户端推送到服务器端&#xff0c;而服务器端不能主动推送到客户端&#xff09;&#xff0c;导致如果服务器如果有连续的变化&#xff0c;这时只能使用轮询&#xff0c;而轮询效率过低&#xff0c;并不适合。于是 WebSocket…

Git 案例(企业如何使用git开发项目)

一、企业中我们是如何开发 1) 入职第一天,管理人员分配/git账号密码 2) 开发人员下载代码即文档/ 根据文档将环境搭建成功 3) 团队一般会给你讲讲项目相关的支持 4) 你接到第一个需求(或者某个功能,一般要经过沟通,分析,设计...等过程) 5) 创建feature分支(一般一个需求对应…

FPGA高端项目:图像采集+GTP+UDP架构,高速接口以太网视频传输,提供2套工程源码加QT上位机源码和技术支持

目录 1、前言免责声明本项目特点 2、相关方案推荐我这里已有的 GT 高速接口解决方案我这里已有的以太网方案 3、设计思路框架设计框图视频源选择OV5640摄像头配置及采集动态彩条视频数据组包GTP 全网最细解读GTP 基本结构GTP 发送和接收处理流程GTP 的参考时钟GTP 发送接口GTP …

【PyQt学习篇 · ⑪】:QPushButton和QCommandLinkButton的使用

文章目录 构造函数菜单设置扁平化默认处理右键菜单QCommandLinkButton的使用 构造函数 QPushButton的构造函数如下&#xff1a; """QPushButton(parent: Optional[QWidget] None)QPushButton(text: Optional[str], parent: Optional[QWidget] None)QPushButt…

自定义的卷积神经网络模型CNN,对图片进行分类并使用图片进行测试模型-适合入门,从模型到训练再到测试,开源项目

自定义的卷积神经网络模型CNN&#xff0c;对图片进行分类并使用图片进行测试模型-适合入门&#xff0c;从模型到训练再到测试&#xff1a;开源项目 开源项目完整代码及基础教程&#xff1a; https://mbd.pub/o/bread/ZZWclp5x CNN模型&#xff1a; 1.导入必要的库和模块&…

进阶C语言-指针的进阶(三)

模拟实现qsort函数 &#x1f388;1.测试bubble_sort&#xff0c;排序整型数组&#x1f388;2测试bubble_sort&#xff0c;排序结构体数组 &#x1f4dd;关于qsort函数&#xff0c;我们可以先去cpluplus网站上面了解一下&#xff1a; //1.排序整型数组&#xff0c;两个整型可以…

玻色量子“天工量子大脑”亮相中关村论坛,大放异彩

2023年5月25日至30日&#xff0c;2023中关村论坛&#xff08;科博会&#xff09;在北京盛大召开。中关村论坛&#xff08;科博会&#xff09;是面向全球科技创新交流合作的国家级平台行业盛会&#xff0c;由科技部、国家发展改革委、工业和信息化部、国务院国资委、中国科学院、…

使用Selenium IDE录制脚本

今天&#xff0c;我们开始介绍基于开源Selenium工具的Web网站自动化测试。 Selenium包含了3大组件&#xff0c;分别为&#xff1a;1. Selenium IDE 基于Chrome和Firefox扩展的集成开发环境&#xff0c;可以录制、回放和导出不同语言的测试脚本。 2. WebDriver 包括一组为不同…

项目实战:展示第一页数据

1、在FruitDao接口中添加查询第一页数据和查询总记录条数 package com.csdn.fruit.dao; import com.csdn.fruit.pojo.Fruit; import java.util.List; //dao &#xff1a;Data Access Object 数据访问对象 //接口设计 public interface FruitDao {void addFruit(Fruit fruit);vo…

Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍

目录 一. Redis简介 二. Redis的安装 2.1 Linux版安装 2.2 windows版安装 三. Redis的常用命令 一. Redis简介 Redis是一个开源&#xff08;BSD许可&#xff09;&#xff0c;内存存储的数据结构服务器&#xff0c;可用作数据库&#xff0c;高速缓存和消息队列代理。 它…

【ES专题】ElasticSearch搜索进阶

目录 前言阅读导航前置知识特别提醒笔记正文一、分词器详解1.1 基本概念1.2 分词发生的时期1.3 分词器的组成1.3.1 切词器&#xff1a;Tokenizer1.3.2 词项过滤器&#xff1a;Token Filter1.3.3 字符过滤器&#xff1a;Character Filter 1.4 倒排索引的数据结构 <font color…

管理文件:文件批量重命名,轻松删除文件名中的空格

在文件管理中&#xff0c;我们经常会遇到文件名中带有空格的情况。这些空格可能会使文件在某些情况下难以被正确识别或使用&#xff0c;因此我们需要掌握一些技巧来轻松删除文件名中的空格。现在使用云炫文件管理器批量重命名进行批量处理。以下是如何操作的步骤详解&#xff1…

网易按照作者批量采集新闻资讯软件说明文档

大家好&#xff0c;我是淘小白~ 今天给大家介绍的爬虫软件是网易按照作者采集的软件 1、软件语言&#xff1a; Python 2、使用到的工具 Python selenium库、谷歌浏览器、谷歌浏览器驱动 3、文件说明&#xff1a; 4、配置文件说明&#xff1a; 5、环境配置 安装Python&am…

基于8051单片机与1601LCD的计算器设计

**单片机设计介绍&#xff0c;1665基于8051单片机与1601LCD的计算器设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于8051单片机和1601LCD的计算器设计是一种简单的嵌入式系统应用&#xff0c;其设计过程包括以下几个步骤…