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

编程入门 行业动态 更新时间:2024-10-28 17:23:53

SpringBoot框架使用AOP + <a href=https://www.elefans.com/category/jswz/34/1771438.html style=自定义注解实现请求日志记录"/>

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和自定义注解实现日志记录就成功啦;目前是直接在控制台打印,也可以将其信息保存到数据库中;

更多推荐

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

本文发布于:2023-11-16 16:41:59,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1627772.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:自定义   注解   框架   日志   SpringBoot

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!