利用注解进行 aop log

2022/03/13 Spring 共 3106 字,约 9 分钟

前言

一般来说,Spring boot 项目的所有 Controller 调用,入参要统一进行 log 记录,也可以纪录方法花费的时间,定位问题,我们可以借助 aop 以及反射,将其统一处理,不需要每一个方法都写一遍 log。这可以说是 aop 最典型的实践。

在此基础之上,还可以再将其进一步简化,利用注解,只要加了注解的方法都可以进行 log,更加简洁。

注解

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
    /**
     * 模块
     */
    String title() default "";

    /**
     * 功能
     */
    String action() default "";
    // 各种需要标记,记录的信息
    ……
}

当 Controller 中的方法需要 log 的时候,加上注解,就会在受到调用的时候自动记录相关信息。

日志实体类

package com.example.ccf.vo;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class SysLog {

    private String method;

    private String params;

    private String operation;

    private String result;

    private long during;
}

根据自己的需要添加属性。

切片

package com.example.ccf.controller;

import com.alibaba.fastjson.JSON;
import com.example.ccf.annotion.LogAnnotation;
import com.example.ccf.vo.SysLog;
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.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@Aspect
@Component
@Slf4j
public class SysLogAspect {
    @Pointcut("@annotation(com.example.ccf.annotion.LogAnnotation)")
    public void logPointCut(){}

    /**
     * 环绕增强,相当于MethodInterceptor
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        SysLog sysLog = new SysLog();
        //请求的方法名
        String className = point.getTarget().getClass().getName();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");

        // 1. 方法调用前,打印请求参数
        try {
            // 请求的参数
            Object[] args = point.getArgs();
            // 去除掉不能序列化的参数
            List<Object> arguments = new ArrayList<>();
            String params = null;
            if (args != null && args.length != 0) {
                for (Object arg : args) {
                    if (arg instanceof ServletRequest || arg instanceof ServletResponse || arg instanceof MultipartFile) {
                        continue;
                    }
                    arguments.add(arg);
                }
                params = JSON.toJSONString(arguments);
            }
            sysLog.setParams(params);

            log.info("调用方法: {}, 请求参数: {}", sysLog.getMethod(), sysLog.getParams());
        } catch (Exception e) {
            log.error("调用方法: " + sysLog.getMethod(), e);
        }

        // 实际调用方法, 如果发生异常则抛出
        Object result = point.proceed();

        // 2. 方法调用后,打印响应结果
        try {
            LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
            if (logAnnotation != null) {
                //注解上的描述
                sysLog.setOperation(logAnnotation.title() + "-" + logAnnotation.action());
            }

            String resultParam = JSON.toJSONString(result);
            sysLog.setResult(resultParam);
            //执行时长(毫秒)
            long time = System.currentTimeMillis() - beginTime;
            sysLog.setDuring(time);
            log.info("调用方法: {}, 响应结果: {},响应时间{}ms",
                    sysLog.getMethod(), sysLog.getResult(), sysLog.getDuring());
        } catch (Exception e) {
            log.error("调用方法: " + sysLog.getMethod(), e);
        }

        return result;
    }

}

具体的记录过程,通过反射获取 method 的相关信息,包括方法名称,参数,等等,在执行方法完成后记录响应结果与花费时间。

如果有其他需要也可以自行增加 log 内容其实有些HandlerMethodArgumentResolver的工作也能放在这里做,也能实现但不是很合适,还是放在它应该在的地方吧。

也可以根据需要储存sysLog对象。

文档信息

Search

    Table of Contents