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.mzph.cn/news/130365.shtml

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

相关文章

2023年软件系统架构师论文【回忆版】

2023年11月5日&#xff0c;全国计算机等级下半年考试&#xff0c;北京市软件架构师考试其中有个考点在首都经济贸易大学丰台校区&#xff09;&#xff0c;地址&#xff1a;北京市丰台区花乡张家路口121号&#xff08;北门入校&#xff09; 注意&#xff1a;机考的考试时间有所变…

BIOS开发笔记 - HDA Audio

在PC中,音频输出是一个重要的功能之一,目前大多数采用的是英特尔高清晰音效(英语:Intel High Definition Audio,简称为HD Audio或IHD)方案,它是由Intel于2004年所提出的音效技术,能够展现高清晰度的音质效果,且能进行多声道的播放,在音质(音效质量)上超越过去的其他…

FFmpeg 硬件加速视频转码指南

基于 Windows 下演示&#xff0c;Linux 下也可以适用。 所使用 ffmpeg 版本为 BtbN 编译的 win64-gpl 版&#xff08;非 gpl-share&#xff09;&#xff0c;项目地址&#xff1a;BtbN / FFmpeg-Builds 也可以使用 gyan.dev 编译的 git-full 版&#xff0c;地址&#xff1a;gyan…

【自动控制原理】时域分析法:一阶、二阶、高阶系统的时间响应及动态性能

文章目录 第3章 时域分析法3.1 基本概念3.1.1 典型输入信号3.1.2 系统动态性能指标 3.2 一阶系统的时间响应及动态性能一阶系统的标准形式&#xff08;尾1标准型&#xff09;3.2.1一阶惯性环节的单位阶跃响应3.2.2一阶惯性环节的单位速度响应3.2.3一阶惯性环节的单位脉冲响应3.…

【gpt redis】原理篇

用的黑马程序员redis课程的目录&#xff0c;但是不想听讲了。后续都是用gpt文档获取的。 1.课程介绍(Av766995956,P145) 2.Redis数据结构-动态字符串(Av766995956,P146) sds 1M是个界限 其实他是个由c语言实现的结构体 有这么几个参数 len alloc flag char[] len是实际长度 …

idea插件(一)-- SequenceDiagram(UML自动生成工具)

目录 1. 安装 2. 默认快捷键 3. 操作说明 4. 导出为图片与UML类图 4.1 导出为图片&#xff1a; 4.2 导出 UML 类图 SequenceDiagram是从java、kotlin、scala&#xff08;Beta&#xff09;和groovy&#xff08;limited&#xff09;代码生成简单序列图&#xff08;UML&…

WorkManger学习汇总

一.使用 WorkManger主要是用来执行一定会执行的任务的&#xff0c;如即使app被杀掉、手机重启都会执行。 适用场景&#xff1a;定期重复性任务&#xff0c;如定期log上传等 使用的话首先引入库&#xff08;我使用的是2.7.1&#xff09; implementation "androidx.work:…

C语言--判断一个年份是否是闰年(详解)

一.闰年的定义 闰年是指在公历&#xff08;格里高利历&#xff09;中&#xff0c;年份可以被4整除但不能被100整除的年份&#xff0c;或者可以被400整除的年份。简单来说&#xff0c;闰年是一个比平年多出一天的年份&#xff0c;即2月有29天。闰年的目的是校准公历与地球公转周…

Elasticsearch 8.X 如何生成 TB 级的测试数据 ?

1、实战问题 我只想插入大量的测试数据&#xff0c;不是想测试性能&#xff0c;有没有自动办法生成TB级别的测试数据&#xff1f;有工具&#xff1f;还是说有测试数据集之类的东西&#xff1f;——问题来源于 Elasticsearch 中文社区https://elasticsearch.cn/question/13129 2…

《视觉SLAM十四讲》-- 概述与预备知识

文章目录 01 概述与预备知识1.1 SLAM 是什么1.1.1 基本概念1.1.2 视觉 SLAM 框架1.1.3 SLAM 问题的数学表述 1.2 实践&#xff1a;编程基基础1.3 课后习题 01 概述与预备知识 1.1 SLAM 是什么 1.1.1 基本概念 &#xff08;1&#xff09;SLAM 是 Simultaneous Localization a…

uniapp 微信小程ios端键盘弹起后导致页面无法滚动

项目业务逻辑和出现的问题整理 新增页面 用户可以主动添加输入文本框 添加多了就会导致当前页面出现滚动条,这就导致ios端滚动页面的时候去点击输入框键盘抬起再关闭的时候去滚动页面发现页面滚动不了(偶尔出现),经过多次测试发现是键盘抬起的时候 主动向上滑动 100%出现这种问…

【Algorithm】最容易理解的蒙特卡洛树搜索(Monte Carlo Tree Search,MCTS)算法

看了不少解读和笔记&#xff0c;本文把最容易理解的解读做个总结。 1. 蒙特卡洛方法 蒙特卡洛方法(Monte Carlo method)&#xff0c;是一种“统计模拟方法”。20世纪40年代&#xff0c;为建造核武器&#xff0c;冯.诺伊曼 等人发明了该算法。因赌城蒙特卡洛而得名&#xff0c…

20.5 OpenSSL 套接字RSA加密传输

RSA算法同样可以用于加密传输&#xff0c;但此类加密算法虽然非常安全&#xff0c;但通常不会用于大量的数据传输&#xff0c;这是因为RSA算法加解密过程涉及大量的数学运算&#xff0c;尤其是模幂运算&#xff08;即计算大数的幂模运算&#xff09;&#xff0c;这些运算对于计…

Openlayers--自定义修改天地图颜色

自定义修改地图颜色 前言效果图1、给titleLayer设置className2、给class设置样式 前言 本篇文章讲解怎样调整地图颜色 效果图 调整前 调整后 1、给titleLayer设置className const arcGISLayer new TileLayer({className:blueLayer,//增加className属性source: new XYZ(…

LV.12 D16 轮询与中断 学习笔记

一、CPU与硬件的交互方式 轮询 CPU执行程序时不断地询问硬件是否需要其服务&#xff0c;若需要则给予其服务&#xff0c;若不需要一段时间后再次询问&#xff0c;周而复始 中断 CPU执行程序时若硬件需要其服务&#xff0c;对应的硬件给CPU发送中断信号&#xff0c…

订单业务和系统设计(一)

一、背景简介 订单其实很常见&#xff0c;在电商购物、外卖点餐、手机话费充值等生活场景中&#xff0c;都能见到它的影子。那么&#xff0c;一笔订单的交易过程是什么样子的呢&#xff1f;文章尝试从订单业务架构和产品功能流程&#xff0c;描述对订单的理解。 二、订单业务…

python自动化运维——模拟键盘鼠标重复性操作Pyautoui

一、程序样式展示 将程序与cmd.xls文件放在同一文件夹&#xff0c;每一步的截图也放在当前文件夹 通过图片在屏幕上面进行比对&#xff0c;找到点击处进行自动化操作 自动化rpa测试 二、核心点 1.Pyautoui模块&#xff1a;主要针对图片进行定位pyautogui.locateCenterOnScree…

Ubuntu系统HUSTOJ 用 vim 修改php.ini 重启PHP服务

cd / sudo find -name php.ini 输出&#xff1a; ./etc/php/7.4/cli/php.ini ./etc/php/7.4/fpm/php.ini sudo vim /etc/php/7.4/cli/php.ini sudo vim /etc/php/7.4/fpm/php.ini 知识准备&#xff1a; vim的搜索与替换 在正常模式下键入 / &#xff0c;即可进入搜索模式…

竞赛选题 深度学习实现行人重识别 - python opencv yolo Reid

文章目录 0 前言1 课题背景2 效果展示3 行人检测4 行人重识别5 其他工具6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的行人重识别算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c…

树莓派结合Nginx,轻松搭建内网穿透服务实现远程访问内部站点

文章目录 1. Nginx安装2. 安装cpolar3.配置域名访问Nginx4. 固定域名访问5. 配置静态站点 安装 Nginx&#xff08;发音为“engine-x”&#xff09;可以将您的树莓派变成一个强大的 Web 服务器&#xff0c;可以用于托管网站或 Web 应用程序。相比其他 Web 服务器&#xff0c;Ngi…