错误内容
今天使用Gson序列化的时候出现了stackOverflowError的错误,内容如下:
at com.google.gson.Gson.getAdapter(Gson.java:416)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getFieldAdapter(ReflectiveTypeAdapterFactory.java:135)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.<init>(ReflectiveTypeAdapterFactory.java:105)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:104)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:96)
at com.google.gson.Gson.getAdapter(Gson.java:416)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getFieldAdapter(ReflectiveTypeAdapterFactory.java:135)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.<init>(ReflectiveTypeAdapterFactory.java:105)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:104)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:96)
at com.google.gson.Gson.getAdapter(Gson.java:416)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getFieldAdapter(ReflectiveTypeAdapterFactory.java:135)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.<init>(ReflectiveTypeAdapterFactory.java:105)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:104)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:96)
at com.google.gson.Gson.getAdapter(Gson.java:416)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getFieldAdapter(ReflectiveTypeAdapterFactory.java:135)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.<init>(ReflectiveTypeAdapterFactory.java:105)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:104)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:160)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:96)
at com.google.gson.Gson.getAdapter(Gson.java:416)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getFieldAdapter(ReflectiveTypeAdapterFactory.java:135)
这个问题我看了很久都没找出问题,终于在同事的帮助下解决了
场景再现
当时是打算写一个切面打印Log,将controller接收的参数使用Gson序列化,注意这个参数是MultipartFile的文件类型,场景如下:
HelloController类:
@Slf4j
@RestController
@RequestMapping("hello")
public class HelloController {
@Autowired
private HelloService helloService;
@PostMapping("dosomething")
public Result hello(@RequestParam("file") MultipartFile multipartFile) {
// doSomething();
}
}
Log切面:
@Aspect
@Component
@Slf4j
public class LogAspect {
@Pointcut("execution(* com.controller..*.*(..))")
private void helloController() {
}
@Around("helloController()")
public Object aroundQueryLog(ProceedingJoinPoint pjp) throws Throwable {
// 逻辑处理。。。。。。。。。。。
Object[] args = pjp.getArgs(); // 获取HelloController的各个参数
for (Object arg : args) {
log.info(new Gson().toJson(arg)); // 序列化每一个参数值
}
// 逻辑处理。。。。。。。。。。
}
}
问题再现并分析
问题:
每次调用hello/dosomething资源,都会报错,就是上文提到的stackOverflowError
分析:
- 对于MultipartFile类型的参数,Tomcat会在一个特定的目录下临时存放这个文件,正常情况下当访问结束后还会存在一段时间
- 由于某些原因切面走到序列化参数值之前,controller已经执行完毕并响应结果
- 公司的服务器在访问结束后,立马删除了这个临时文件
所以当序列化参数值的时候,MultipartFile对象对应的临时文件不存在了,相当于序列化了一个寂寞。
那么导致出现stackOverflowError的元凶就找出来了。‘
从Gson User Guide的解释上看:
If a field is marked transient, (by default) it is ignored and not included in the JSON serialization or deserialization.
…
By default, if you mark a field as transient, it will be excluded. As
well, if a field is marked as “static” then by default it will be
excluded.
像这种只存在瞬间的对象,最好排除在序列化之外(这里有一个瞬态和静态的概念)
ps : 不知道是否理解正常,但是当看见这句话时,确实是想到了有一个存在临时文件的可能~
解决方案
- 要么不序列化带有MultipartFile的参数
- 要么使用Base64将输出流转化为Base64格式进行文件的传递与接收
可以根据这个网站进行操作,也可以看我下面这段代码
https://blog.csdn.net/weixin_45562753/article/details/108255263
/**
* 将文件转成base64编码
*
*/
public String getBase64Code(ByteArrayOutputStream byteArrayOutputStream){
try {
byte[] bytes = byteArrayOutputStream.toByteArray();
String base64Str = Base64.getEncoder().encodeToString(bytes);
return base64Str;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
总结
Gson能够导致stackOverflowError的情况,我就见过两种:
- 循环引用造成,这个情况的博客有很多了
- 遇见临时文件被删除(临时性的)导致