使用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 通常用于回收资源,如关闭数据库连接等操作。

相关推荐
经典小熊1 分钟前
接手同事代码1小时后,我发现了这个隐藏的return...
后端
编码忘我2 分钟前
java之线程池
java·后端·面试
程序员水自流3 分钟前
【AI大模型第13集】Transformer底层架构原理详细介绍(核心组件拆解分析)
java·人工智能·架构·llm·transformer
岁岁种桃花儿5 分钟前
kubenetes从入门到上天系列第二十六篇:Kubernetes的Istio服务网格实战
java·kubernetes·istio
JavaEdge.7 分钟前
openclaw 本地部署实战:含网关启动 + 本地模型接入完整步骤
java·openclaw
Carsene8 分钟前
🎉 AutoScan v1.1.0 发布 - 通配符包扫描、排除过滤、自定义注解三大新特性
spring boot·后端
loserwang8 分钟前
Fluss#1386: 从日志恢复中的 OutOfOrder 来看 LEO、HW 与 Checkpoint 的区别
java·后端
PaytonD9 分钟前
基于 GPUI 实现 WebScoket 服务端之服务篇
后端·rust
毕设源码-邱学长9 分钟前
【开题答辩全过程】以 咖啡馆管理系统的设计与实现为例,包含答辩的问题和答案
java
NGC_661114 分钟前
JDK1.7 与 JDK1.8 ConcurrentHashMap:从分段锁到桶级锁的进化
java·开发语言