需求:
记录所有接口请求的参数,返回内容以及耗时,用于统计响应慢的接口
采用方案:
Aop 切面所有Controller,代码如下:
/**
* 记录接口耗时
*/
@Slf4j
@Aspect
@RefreshScope
public class ControllerLogAspect {
@Autowired
private AsyncLogService logService;
@Value("${spring.application.name}")
private String serverName;
@Value("${interface.aop.save.threshold}")
private int saveThreshold;
/**
* 计算操作消耗时间
*/
private static final ThreadLocal<Long> TIME_THREAD_LOCAL = new NamedThreadLocal<Long>("Cost Time");
/**
* 切点 所有controller 都被切中
*/
@Pointcut("execution(* com.xxx.*.controller.*.*(..))")
public void cf(){}
/**
* 处理请求前执行
*/
@Before(value = "cf()")
public void boBefore(JoinPoint joinPoint) {
TIME_THREAD_LOCAL.set(System.currentTimeMillis());
}
/**
* 接口执行后执行
* @param joinPoint
* @return
* @throws Throwable
*/
@AfterReturning(pointcut = "cf()",returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint,Object jsonResult) {
try {
if(TIME_THREAD_LOCAL.get()!=null){
Long cost = System.currentTimeMillis() - TIME_THREAD_LOCAL.get();
if(cost>=saveThreshold){
//大于1s的请求才会记录
ControllerLog controllerLog = new ControllerLog();
controllerLog.setServerName(serverName);
controllerLog.setClassName(joinPoint.getTarget().getClass().getName());
controllerLog.setMethodName(joinPoint.getSignature().getName());
controllerLog.setOperUrl(ServletUtils.getRequest().getMethod()+" "+ServletUtils.getRequest().getRequestURI());
controllerLog.setCostTime(cost);
setRequestValue(joinPoint,controllerLog);
controllerLog.setOperResult(JSON.toJSONString(jsonResult));
logService.saveControllerLog(controllerLog);
}
}
}catch (Exception e){
log.error("接口日志记录异常信息:{}", e.getMessage(),e);
}finally {
TIME_THREAD_LOCAL.remove();
}
}
private void setRequestValue(JoinPoint joinPoint, ControllerLog controllerLog) throws Exception {
String requestMethod = ServletUtils.getRequest().getMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
controllerLog.setOperParams(params);
} else {
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
controllerLog.setOperParams(JSON.toJSONString(paramsMap));
}
}
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
try {
String jsonObj = JSON.toJSONString(o);
params += jsonObj.toString() + " ";
} catch (Exception e) {
}
}
}
}
return params.trim();
}
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
@AfterThrowing(value = "cf()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
//异常的不记录,会发异常日志且会被全局异常捕捉
}
}
很简单的AOP实现,结果测试断点发现 @Before @AfterReturning 都被执行了两次。
原因:
里面有个阈值,配置在nacos,想实现热更新,避免重启服务。就是因为使用了@RefreshScope这个注解,导致这个Aspect 生成了两份代理。
解决方法:去掉 @RefreshScope 注解