使用 AOP(面向切面编程)技术对所有控制器方法的执行进行日志记录

使用 AOP(面向切面编程)技术在 Spring Boot 应用程序中对所有控制器方法的执行进行日志记录。具体来说,它拦截所有控制器中的公共方法,记录方法的输入参数和返回结果

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.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

@Aspect
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MethodExecutionLogAspect {

    @Resource
    private ObjectMapper objectMapper;

    // 切入点拦截所有 Controller 方法
    @Pointcut("execution(public * com.example.*.controller.*Controller.*(..))")
    private void controller() {
    }

    @Around("controller()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        logMethodEntry(joinPoint);
        Object result = joinPoint.proceed();
        logMethodExit(result);
        return result;
    }

    private void logMethodEntry(ProceedingJoinPoint joinPoint) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            String[] parameterNames = signature.getParameterNames();
            String params = objectToString(joinPoint.getArgs());
            log.info("==============处理方法入参:{} {}", parameterNames, params);
        } catch (Exception ex) {
            log.error("AOP 在方法入参日志记录时出错: {}", ex.getMessage(), ex);
        }
    }

    private void logMethodExit(Object result) {
        try {
            log.info("==============处理方法出参: {}", objectToString(new Object[]{result}));
        } catch (Exception ex) {
            log.error("AOP 在方法出参日志记录时出错: {}", ex.getMessage(), ex);
        }
    }

    private String objectToString(Object[] objs) {
        if (objs == null || objs.length == 0) {
            return "empty";
        }

        StringBuilder stringBuilder = new StringBuilder("[");
        for (Object obj : objs) {
            if (stringBuilder.length() > 1000) {
                stringBuilder.append("...太长省略");
                break;
            }

            String s;
            if (obj == null) {
                s = "{}";
            } else if (obj instanceof ServletResponse) {
                s = "{Response}";
            } else if (obj instanceof ServletRequest) {
                s = "{Request}";
            } else {
                s = convertObjectToString(obj);
            }

            stringBuilder.append(s).append(",");
        }

        int length = stringBuilder.length();
        if (length > 1) {
            stringBuilder.deleteCharAt(length - 1);  // 移除最后一个逗号
        }
        stringBuilder.append("]");

        return stringBuilder.toString();
    }

    private String convertObjectToString(Object obj) {
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            return obj.toString();
        }
    }
}

主要作用

  1. 日志记录:在方法执行之前记录方法的输入参数,在方法执行之后记录返回结果。
  2. 增强方法执行的可调试性和可追踪性:通过记录输入和输出,方便开发人员调试和跟踪方法的执行情况。

详细解释

  1. 定义切面(Aspect)

    • @Aspect:声明这是一个切面类。
    • @Component:将这个切面类声明为 Spring 容器中的一个 Bean。
    • @Slf4j:提供日志记录功能,使用 log 对象记录日志。
    • @Order(Ordered.HIGHEST_PRECEDENCE):设置切面的优先级为最高。
  2. 定义切入点(Pointcut)

    • @Pointcut("execution(public * com.example.*.controller.*Controller.*(..))"):定义一个切入点,拦截所有 com.example.*.controller 包及其子包下的控制器类中的公共方法。
  3. 环绕通知(Around Advice)

    • @Around("controller()"):定义一个环绕通知,围绕在指定切入点的方法执行前后。
    • around(ProceedingJoinPoint joinPoint):环绕通知方法,接收一个 ProceedingJoinPoint 参数,它提供对被拦截方法的访问。
  4. 方法入参日志记录

    • logMethodEntry(joinPoint):记录方法的输入参数。
      • 获取方法签名和参数名称。
      • 将参数转换为字符串。
      • 记录日志。
  5. 方法执行

    • Object result = joinPoint.proceed();:执行被拦截的方法,并获取返回结果。
  6. 方法出参日志记录

    • logMethodExit(result):记录方法的返回结果。
  7. 参数和结果的转换

    • objectToString(Object[] objs):将方法参数或返回结果转换为字符串,处理 null 值、ServletRequestServletResponse 对象。
    • convertObjectToString(Object obj):将对象转换为 JSON 字符串,如果转换失败则调用 toString() 方法。

代码的意义

这段代码对于开发和运维团队来说非常有用:

  • 调试:当方法执行出现问题时,通过日志可以快速定位问题,查看方法的输入和输出。
  • 审计:记录所有方法调用的输入输出,可以用于审计和追踪。
  • 监控:通过记录方法执行的详细信息,可以监控系统的运行状态和性能。

示例日志输出

执行日志可能会如下:

INFO  MethodExecutionLogAspect - ==============处理方法入参:["input"] ["example"]
INFO  MethodExecutionLogAspect - ==============处理方法出参: ["Hello example"]

相关推荐

  1. AOP面向切面编程

    2024-07-17 10:42:04       32 阅读
  2. AOP面向切面编程代码实现

    2024-07-17 10:42:04       18 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 10:42:04       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 10:42:04       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 10:42:04       58 阅读
  4. Python语言-面向对象

    2024-07-17 10:42:04       69 阅读

热门阅读

  1. NLP篇5:自然语言处理预训练

    2024-07-17 10:42:04       21 阅读
  2. N叉树的前序遍历

    2024-07-17 10:42:04       27 阅读
  3. CopyOnWriteArrayList

    2024-07-17 10:42:04       22 阅读
  4. Qt编程技巧总结篇(4)-信号-槽-多线程(三)

    2024-07-17 10:42:04       23 阅读
  5. 【面试题】Golang之互斥锁与读写锁(第七篇)

    2024-07-17 10:42:04       22 阅读
  6. windows 安装 tensorflow 报错说 C++

    2024-07-17 10:42:04       23 阅读
  7. Vue 和 React 框架实现滚动缓冲区

    2024-07-17 10:42:04       21 阅读
  8. Mysql什么情况下会发生死锁,又该怎么解决?

    2024-07-17 10:42:04       25 阅读
  9. 服务器上有多个nginx,如何知道启动的是哪个?

    2024-07-17 10:42:04       25 阅读