本文用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:博主这样的做法被同事否定了,原因是这样浪费了资源,降低了性能。等待博主寻找到更好的记录参数的做法再与大家分享,感谢观看,如果有帮助不妨点个赞
时间没有放过任何人,你欠岁月的将以另一种形式偿还。