FastJson、Jackson使用AOP切面进行日志打印异常
FastJson、Jackson使用AOP切面进行日志打印异常
一、概述
1、问题详情
使用FastJson、Jackson进行日志打印时分别包如下错误:
源码:
//fastjon
log.info("\nRequest Info :{} \n", JSON.toJSONString(requestInfo));
//jackson
log.info("\nRequest Info :{} \n", new ObjectMapper().writeValueAsString(requestInfo));
-
Fastjson错误信息:
java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false) at org.apache.catalina.connector.Request.getAsyncContext(Request.java:1812) at org.apache.catalina.connector.RequestFacade.getAsyncContext(RequestFacade.java:1068)
-
Jackson错误信息:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.util.Collections$3 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: icu.chiou.qvideo.aop.AccessLogAspect$RequestInfo["requestParams"]->java.util.HashMap["request"]->org.apache.catalina.connector.RequestFacade["parameterNames"])
2、问题原因
猜测:
这些错误的原因可能是由于在日志打印过程中,引入了 Web 请求的相关对象,而这些对象无法直接被序列化为 JSON。
查找:
- 调用getAsyncContext方法出现了问题,表示当前这个request对象不是异步模式的,所以不能调用
getAsyncContext
这个方法,这个request的异步模式这是servlet3中的一个新特性,可以使用注解或者配置xml的方式进行开启,一般用于异步请求,这个request的异步模式的使用场景,fastjson序列化出错,出错的原因是fastjson调用了getAsyncContext
方法,由于request不是异步模式,所以报错了,那么结果就是fastjson序列化出错了。 - jackson异常信息表明 jackson 无法找到适合序列化
java.util.Collections$3
这个类的方法。这种异常通常出现在对象中包含了无法序列化的属性或者属性类型,导致 Jackson 无法将整个对象序列化为 JSON 字符串。在你的异常信息中,可以看到异常发生在AccessLogAspect
的RequestInfo
对象的requestParams
属性中,其中包含了一个java.util.HashMap
对象,该对象中的属性为request
,类型为org.apache.catalina.connector.RequestFacade
,进而包含了parameterNames
属性,类型为java.util.Collections$3
,而 Jackson 无法对这个属性进行序列化。
二、解决
1、问题定位
看一下异常信息,按照打印的栈信息来看:
- 是
RequestFacade
对象中的getAsyncContext
方法被调用了,但是工程里面并没用用到RequestFacade
对象,且其没有重载方法 - 查看
RequestFacade
类的源码过后发现RequestFacade
其实是HttpServletRequest
的一个具体实现 - 那么问题就定位到了,fastjson把
HttpServletRequest
序列化了,只要把方法上的HttpServletRequest
参数去掉就可以了.
2、解决办法
把进行JSON转字符串序列化对象内的request过滤即可:
private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
//参数名
String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
//参数值
Object[] paramValues = joinPoint.getArgs();
return buildRequestParam(paramNames, paramValues);
}
private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
Map<String, Object> requestParams = new HashMap<>();
for (int i = 0; i < paramNames.length; i++) {
Object value = paramValues[i];
//添加这个
if (value instanceof ServletRequest || value instanceof ServletResponse) {
continue;
}
//如果是文件对象
if (value instanceof MultipartFile) {
MultipartFile file = (MultipartFile) value;
value = file.getOriginalFilename(); //获取文件名
}
requestParams.put(paramNames[i], value);
}
return requestParams;
}
3、效果实现
源代码和拓展:
需要控制台格式化的输出JSON只需要调用:writerWithDefaultPrettyPrinter()
log.info("\nRequest Info :{} \n", new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(requestInfo));
log.info("\nRequest Info :{} \n", JSON.toJSONString(requestInfo));