Spring AOP日志记录接口请求参数,执行时间

本文用spring aop方式对请求拦截,获取请求参数以及计算接口执行时间。注意:所需的环境以及依赖有:spring各包, jdk1.8,org.slf4j.Logger (请执行导入)

前言

在前后端分离的项目中,常因为不知道是前端还是后端的问题,而苦苦寻找bug的根源。如果能在日志中看到前端传过来的参数,就能直观的知道是前端参数的问题还是后台程序的问题,以定位到问题的根源。同时计算执行时间可了解到接口是否符合要求

代码

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;

import java.lang.reflect.Field;
import java.util.Objects;
import java.util.stream.Stream;

/**
* aop采集日志(接口请求参数,接口调用时间)
* @author Huangqing
* @date 2018/7/16 11:50
*/
@Aspect
@Component
public class ControllerInterceptor {

 private static String[] types = {"java.lang.Integer", "java.lang.Double",
         "java.lang.Float", "java.lang.Long", "java.lang.Short",
         "java.lang.Byte", "java.lang.Boolean", "java.lang.Char",
         "java.lang.String", "int", "double", "long", "short", "byte",
         "boolean", "char", "float"};

 private static final Logger log = LoggerFactory.getLogger(ControllerInterceptor.class);
 private static ThreadLocal<Long> startTime = new ThreadLocal<Long>();


 /**
  * 定义拦截规则:拦截com.dxyl.controller..*(..))包下面的所有类中,有@PostMapping注解的方法
  */
 @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
 public void controllerMethodPointcut() {
 }

 @Before("controllerMethodPointcut()")
 public void controller(JoinPoint point) {
     startTime.set(System.currentTimeMillis());
     MethodSignature signature = (MethodSignature) point.getSignature();
     Long count = Stream.of(signature.getMethod().getDeclaredAnnotations())
             .filter(annotation -> annotation.annotationType() == PostMapping.class)
             .count();
     String requestPath = count >= 1 ? signature.getMethod().getAnnotation(PostMapping.class).value()[0] : "";

     String info = String.format("\n =======> 请求路径: %s  %s", requestPath, getMethodInfo(point));
     log.info(info);
 }

 private String getMethodInfo(JoinPoint point) {
     String className = point.getSignature().getDeclaringType().getSimpleName();
     String methodName = point.getSignature().getName();
     String[] parameterNames = ((MethodSignature) point.getSignature()).getParameterNames();
     StringBuilder sb = null;
     if (Objects.nonNull(parameterNames)) {
         sb = new StringBuilder();
         for (int i = 0; i < parameterNames.length; i++) {
             // 对参数解析(参数有可能为基础数据类型,也可能为一个对象,若为对象则需要解析对象中变量名以及值)
             String value = "";
             if (point.getArgs()[i] == null) {
                 value = "null";
             } else {
                 // 获取对象类型
                 String typeName = point.getArgs()[i].getClass().getTypeName();
                 boolean flag = false;
                 for (String t : types) {
                     //1 判断是否是基础类型
                     if (t.equals(typeName)) {
                         value = point.getArgs()[i].toString();
                         flag = true;
                     }
                     if (flag) {
                         break;
                     }
                 }
                 if (!flag) {
                     //2 通过反射获取实体类属性
                     value = getFieldsValue(point.getArgs()[i]);
                 }
             }
             sb.append(parameterNames[i] + ":" + value + "; ");
         }
     }
     sb = sb == null ? new StringBuilder() : sb;
     String info = String.format("\n =======> 请求类名: %s \n =======> 请求方法: %s \n =======> 请求参数: %s", className, methodName, sb.toString());
     return info;
 }

 /**
  *  解析实体类,获取实体类中的属性
  */
 public static String getFieldsValue(Object obj) {
     //通过反射获取所有的字段,getFileds()获取public的修饰的字段
     //getDeclaredFields获取private protected public修饰的字段
     Field[] fields = obj.getClass().getDeclaredFields();
     String typeName = obj.getClass().getTypeName();
     for (String t : types) {
         if (t.equals(typeName)) {
             return "";
         }
     }
     StringBuilder sb = new StringBuilder();
     sb.append("{");
     for (Field f : fields) {
         //在反射时能访问私有变量
         f.setAccessible(true);
         try {
             for (String str : types) {
                 //这边会有问题,如果实体类里面继续包含实体类,这边就没法获取。
                 //其实,我们可以通递归的方式去处理实体类包含实体类的问题。
                 if (f.getType().getName().equals(str)) {
                     sb.append(f.getName() + " : " + f.get(obj) + ", ");
                 }
             }
         } catch (IllegalArgumentException e) {
             e.printStackTrace();
         } catch (IllegalAccessException e) {
             e.printStackTrace();
         }
     }
     sb.append("}");
     return sb.toString();
 }

 /**
  *  计算接口执行时间
  */
 @AfterReturning(pointcut = "controllerMethodPointcut()")
 public void doAfterReturing() {
     long costTime = System.currentTimeMillis() - startTime.get();
     log.info("\n =======> 耗费时间: " + costTime + "ms");
 }
}

测试结果

===2018-07-19 17:42:40.748 [http-nio-8085-exec-3] INFO  com.dxyl.aop.ControllerInterceptor Line:54  - 
 =======> 请求路径: /list  
 =======> 请求类名: TagController 
 =======> 请求方法: getList 
 =======> 请求参数: pageNum:1; pageSize:10; 
===2018-07-19 17:42:41.522 [http-nio-8085-exec-3] INFO  com.dxyl.aop.ControllerInterceptor Line:136 - 
 =======> 耗费时间: 779ms

参数中有实体对象的也进行了解析,但仍存在一些问题,在对象中存在对象则没有解析出来,博主想到的有递归,等有时间去写好了再更新给大家。

PS:博主这样的做法被同事否定了,原因是这样浪费了资源,降低了性能。等待博主寻找到更好的记录参数的做法再与大家分享,感谢观看,如果有帮助不妨点个赞

时间没有放过任何人,你欠岁月的将以另一种形式偿还。

猜你喜欢

转载自blog.csdn.net/qq_20492999/article/details/81120110