使用springboot aop记录接口请求的参数及响应

概述

使用aop做日志记录,记录输入的参数名及参数值,并且记录接口响应结果。

切面类

java 复制代码
package com.zou.metabox.common.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;

/**
 * @author BIGSHU0923
 * @description com.zou.metabox 中Controller层的的日志切面
 * @since 7/30/2023  5:32 PM
 */
@Aspect
@Component
@Slf4j
public class LoggingAspect {
    /**
     * com.zou.metabox.controller 包中公共方法的切入点
     */
    @Pointcut("execution(public * com.zou.metabox.controller.*.*(..))")
    public void loggingPointcut(){
        // 暂不用处理
    }

    /**
     在日志切入点之前执行的通知。
     记录方法名称和请求参数。
     @param joinPoint 控制器类中的连接点
     */
    @Before("loggingPointcut()")
    public void logBefore(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = methodSignature.getParameterNames();
        Map<String, Object> requestMap = new HashMap<>();
        IntStream.range(0, parameterNames.length)
                .forEach(i -> requestMap.put(parameterNames[i], args[i]));
        log.info("Before method:{}|{}", joinPoint.getSignature().getName(), requestMap);
    }

    /**
     在日志切入点之后和方法成功执行后执行的通知。
     记录方法名称和返回结果。
     @param joinPoint 控制器类中的连接点
     @param result 方法的返回结果
     */
    @AfterReturning(pointcut = "execution(public * com.zou.metabox.controller.*.*(..))", returning = "result")
    public void logResponse(JoinPoint joinPoint, Object result){
        log.info("Method response:{}|{}", joinPoint.getSignature().getName(), result.toString());
    }

}

注意

这个切面定义的切点是在@Pointcut这个注解中定义的。我这里定义的是Controller中所有的public方法。

@After和@Afterreturning的区别

@After和@AfterReturning是两个不同的切面通知类型。

@After通知会在目标方法执行之后触发,无论目标方法是否抛出异常。所以在@After通知中不能访问目标方法的返回值。

@AfterReturning通知只在目标方法成功执行并返回后触发,可以访问目标方法的返回值。一般使用@AfterReturning通知来收集方法的执行结果或进行日志记录。

优化

@Around注解可以更方便地控制代理链条的行为,具体是指通过@Around注解实现的方法既可以代替@Before和@AfterReturning注解,也可以控制何时进入、何时退出被代理的方法。在执行目标方法之前,可以在@Around注解标注的方法中编写一些逻辑来决定是否继续执行目标方法,还可以修改传递给目标方法的参数。在执行完目标方法后,可以在@Around注解标注的方法中对返回值进行处理或者抛出异常。

@Around可以控制何时进入、何时退出被代理的方法。具体是通过ProceedingJoinPoint参数调用proceed()方法来执行目标方法。如下面的result = pjp.proceed();这里起始执行的就是接口的方法。所以可以控制何时进入、何时退出被代理的方法。

java 复制代码
    @Around("loggingPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        // 获取类名
        String className = pjp.getTarget().getClass().getTypeName();
        // 获取方法名
        String methodName = pjp.getSignature().getName();
        // 获取参数名
        String[] parameterNames = ((MethodSignature) pjp.getSignature()).getParameterNames();

        Object result = null;
        // 获取参数值
        Object[] args = pjp.getArgs();

        // 获取请求
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 获取请求的url
        String url = request.getRequestURL().toString();

        // 请求参数,以参数名和值为键值对
        Map<String, Object> paramMap = new HashMap<>();
        IntStream.range(0, parameterNames.length).forEach(i->paramMap.put(parameterNames[i], args[i]));

        // header参数
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, Object> headerMap = new HashMap<>();
        while (headerNames.hasMoreElements()){
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            headerMap.put(headerName, headerValue);
        }

        // 打印请求参数,记录起始时间
        long start = System.currentTimeMillis();
        log.info("请求| 请求接口:{} | 类名:{} | 方法:{} | header参数:{} | 参数:{} | 请求时间:{}", url, className, methodName, headerMap, paramMap, LocalDateTime.now());

        try {
            result = pjp.proceed();
        } catch (Exception e) {
            log.error("返回| 处理时间:{} 毫秒 | 返回结果 :{}", (System.currentTimeMillis() - start), "failed");
            throw e;
        }

        // 获取执行完的时间 打印返回报文
        log.info("返回| 处理时间:{} 毫秒 | 返回结果 :{}", (System.currentTimeMillis() - start), "success");
        return result;
    }

总结

Spring AOP 是基于代理的 AOP 框架,提供了几个不同的切面建言(advice)注解:@Before、@AfterReturning、@Around、@AfterThrowing 和 @After。这些注解分别表示在目标方法执行的不同时间点进行增强处理。

具体来说:

@Before:表示在目标方法执行前进行增强处理。

@AfterReturning:表示在目标方法执行后,返回结果之后进行增强处理,可以访问到方法的返回值。

@Around:表示在目标方法执行前、执行中和执行后都可以进行增强处理,并且需要手动控制何时进入、何时退出被代理的方法。

@AfterThrowing:表示在目标方法抛出异常后进行增强处理。

@After:表示在目标方法执行后,无论是否发生异常,都进行增强处理。相较于@AfterReturning,@After增强处理无法获取到方法的返回值。

在实际使用时,@Before 和 @AfterReturning 主要用于记录日志、记性权限控制等与目标方法无关的操作;@Around 则比较常用,因为它可以在目标方法的执行前后进行增强处理,并且可以更方便地控制代理链条的行为;@AfterThrowing 和 @After 通常用于回收资源,如关闭数据库连接等操作。

相关推荐
葫芦和十三12 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp12 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑13 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯13 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan16 小时前
多Agent之间的区别
后端
青石路17 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充18 小时前
1.面向对象设计思想
后端
IT_陈寒18 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro19 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗19 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端