使用 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"]
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Go 中,可以通过将切片作为参数,来实现链式调用。具体实现步骤如下: 1. 定义一个结构体类型,结构体中包含需要链式调用的方法。 2. 在方法中,将当前结构体指针和一个切片作为参数,将当前结构体指针添加到切片中,然后返回切片本身,这样就可以在调用完当前方法后,将当前结构体指针添加到切片中,并直接调用下一个方法。 例如,下面的代码演示了如何定义一个包含链式调用的结构体类型 Person: ```go type Person struct { name string age int } func (p *Person) SetName(name string, chain []interface{}) []interface{} { p.name = name chain = append(chain, p) return chain } func (p *Person) SetAge(age int, chain []interface{}) []interface{} { p.age = age chain = append(chain, p) return chain } ``` 在上面的代码中,SetName 和 SetAge 方法都接受了一个类型为 []interface{} 的切片作为参数,这个切片用于存储调用链中的结构体指针。在方法中,我们将当前结构体指针添加到切片中,并返回切片本身。 下面是如何使用这个结构体类型进行链式调用的示例代码: ```go p := &Person{} chain := []interface{}{} chain = p.SetName("Tom", chain) chain = p.SetAge(20, chain) ``` 在上面的示例代码中,首先创建了一个 Person 类型的指针 p,然后定义了一个类型为 []interface{} 的切片 chain,将 p 作为参数调用 SetName 和 SetAge 方法,将返回值赋值给 chain,这样就完成了链式调用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值