SpringBoot使用自定义注解、AOP实现API接口日志记录

news/2024/11/19 9:33:47/

        在后端服务中,对接口的每次请求入参、方法、出参、执行耗时等信息进行日志打印或记录,是很有必要的。有了日志记录,在后端服务出现问题的时候,可以快速定位问题所在,或者可以通过日志异常快速提醒相关人员进行服务维护。

自定义注解

        Java注解是从JDK5开始引入的,通过它提供的元注解我们可以自定义符合自己使用的自定义注解。

主要元注解说明

  • @Retention(RetentionPolicy.RUNTIME): 这个元注解用于指定注解的保留策略,即注解在何时生效。RetentionPolicy.RUNTIME 表示该注解将在运行时保留,这意味着它可以通过反射在运行时被访问和解析。
  • @Target({ElementType.METHOD}): 这个元注解用于指定注解的目标元素,即可以在哪些地方使用这个注解。ElementType.METHOD 表示该注解只能用于方法上。这意味着您只能在方法上使用这个特定的注解。
  • @Documented: 这个元注解用于指定被注解的元素是否会出现在生成的Java文档中。如果一个注解使用了 @Documented,那么在生成文档时,被注解的元素及其注解信息会被包含在文档中。这可以帮助文档生成工具(如 JavaDoc)在生成文档时展示关于注解的信息。

AOP

        AOP是一种编程范式,它提供了一种能力,让开发者能够模块化跨多个对象的横切关注点(例如日志、事务管理等)

aspectj 注解说明

在配置 AOP 切面之前,我们需要了解下 aspectj 相关注解的作用:

  • @Aspect:声明该类为一个切面类;
  • @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个 package 下的方法;

切点定义好后,就是围绕这个切点做文章了:

  • @Before: 在切点之前,织入相关代码;
  • @After: 在切点之后,织入相关代码;
  • @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
  • @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
  • @Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;

一、添加依赖

        关键在于AOP依赖,Jackson依赖主要用于将出入参转为json字符串

        <!-- AOP 切面 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- Jackson --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency>

二、自定义注解

import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ApiLog {/*** API 功能描述** @return*/String description() default "";}

三、定义JSON工具类

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class JsonUtil {private static final ObjectMapper INSTANCE = new ObjectMapper();public static String toJsonString(Object obj) {try {return INSTANCE.writeValueAsString(obj);} catch (JsonProcessingException e) {return obj.toString();}}
}

四、定义日志切面类

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;@Aspect
@Component
@Slf4j
public class ApiLogAspect {/** 以自定义 @ApiLog 注解为切点,凡是添加 @ApiLog 的方法,都会执行环绕中的代码 */@Pointcut("@annotation(com.smiling.aspect.ApiOperationLog)")public void apiLog() {}/*** 环绕* @param joinPoint* @return* @throws Throwable*/@Around("apiLog()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {try {// 请求开始时间long startTime = System.currentTimeMillis();// MDCMDC.put("traceId", UUID.randomUUID().toString());// 获取被请求的类和方法String className = joinPoint.getTarget().getClass().getSimpleName();String methodName = joinPoint.getSignature().getName();// 请求入参Object[] args = joinPoint.getArgs();// 入参转 JSON 字符串String argsJsonStr = Arrays.stream(args).map(toJsonStr()).collect(Collectors.joining(", "));// 功能描述信息String description = getApiLogDescription(joinPoint);// 打印请求相关参数log.info("====== 请求开始: [{}], 入参: {}, 请求类: {}, 请求方法: {} =================================== ",description, argsJsonStr, className, methodName);// 执行切点方法Object result = joinPoint.proceed();// 执行耗时long executionTime = System.currentTimeMillis() - startTime;// 打印出参等相关信息log.info("====== 请求结束: [{}], 耗时: {}ms, 出参: {} =================================== ",description, executionTime, JsonUtil.toJsonString(result));return result;} finally {MDC.clear();}}/*** 获取注解的描述信息* @param joinPoint* @return*/private String getApiLogDescription(ProceedingJoinPoint joinPoint) {// 1. 从 ProceedingJoinPoint 获取 MethodSignatureMethodSignature signature = (MethodSignature) joinPoint.getSignature();// 2. 使用 MethodSignature 获取当前被注解的 MethodMethod method = signature.getMethod();// 3. 从 Method 中提取 LogExecution 注解ApiOperationLog apiOperationLog = method.getAnnotation(ApiOperationLog.class);// 4. 从 LogExecution 注解中获取 description 属性return apiOperationLog.description();}/*** 转 JSON 字符串* @return*/private Function<Object, String> toJsonStr() {return arg -> JsonUtil.toJsonString(arg);}}

五、使用

在需要进行日志记录的接口上使用自定义注解@ApiLog即可

@RestController
@Slf4j
public class TestController {@PostMapping("/test")@ApiLog(description = "测试接口")public User test(@RequestBody User user) {// 返参return user;}}


http://www.ppmy.cn/news/1260443.html

相关文章

Gitlab 安装手册

MD[Gitlab 安装手册] Gitlab 安装手册 说明: Gitlab最低配置1核2g,建议配置2核4g以上且单独部署,如有多项目CI/CD要求,可以4核8g 1. 安装相关依赖(安装policycoreutils) [rootsjclinux ~]# yum -y install policycoreutils openssh-server openssh-clients postfix 2. 启动s…

【Java】实现顺序表基本的操作(数据结构)

文章目录 前言顺序表1、打印顺序表2、增加元素3、在任意位置增加元素4、判断是否包含某个元素5、查找某个元素对于的位置6、获取任意位置的元素7、将任意位置的元素设为value8、删除第一次出现的关键字9、获取顺序表长度10、清空顺序表总结 前言 在了解顺序表之前我们要先了解…

编织魔法世界——计算机科学的奇幻之旅

文章目录 每日一句正能量前言为什么当初选择计算机行业计算机对自己人生道路的影响后记 每日一句正能量 人生就像赛跑&#xff0c;不在乎你是否第一个到达尽头&#xff0c;而在乎你有没有跑完全程。 前言 计算机是一个神奇的领域&#xff0c;它可以让人们创造出炫酷的虚拟世界…

Linux-网络服务和端口

域名&#xff1a;便于人们记忆和使用的标识符 www.baidu.com域名解析&#xff1a;将域名转换为与之对应的 IP 地址的过程 nameserver 8.8.8.8ip地址&#xff1a;网络设备的唯一数字标识符 域名ip地址localhost127.0.0.1 网络服务和端口 网络服务端口ftp21ssh22http80https…

ERP和MES的区别与联系,这篇接地气的文章终于讲明白了!

一、ERP和MES之间的“区别” ERP和MES系统在企业管理中都扮演着重要的角色&#xff0c;但它们的功能和职责各有不同。 既然今天要聊ERP和MES的区别&#xff0c;那肯定要给大家讲明白了才行。 所以&#xff0c;这里首先得从工厂的业务模式说起。 作为一个工厂&#xff0c;存…

深入理解mysql的explain命令

1 基础 全网最全 | MySQL EXPLAIN 完全解读 1.1 MySQL中EXPLAIN命令提供的字段包括&#xff1a; id&#xff1a;查询的标识符。select_type&#xff1a;查询的类型&#xff08;如SIMPLE, PRIMARY, SUBQUERY等&#xff09;。table&#xff1a;查询的是哪个表。partitions&…

很全面 影响无人机自动返航的因素总结

在无人机技术不断成熟的今天&#xff0c;自主返航技术成为保障飞行安全的一种重要工具。无人机在多种情况下能够智能判断&#xff0c;主动实施返航动作&#xff0c;为用户提供更加可靠的飞行保障。以下是一些常见的无人机自动返航场景&#xff0c;让我们深入了解这项技术背后的…

vue中的动画组件使用及如何在vue中使用animate.css

“< Transition >” 是一个内置组件&#xff0c;这意味着它在任意别的组件中都可以被使用&#xff0c;无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发&#xff1a; 由 v-if 所触发的切换由 v-show 所触…