Gson不支持循环依赖的对象序列化(踩坑日记)

/ 默认分类 / 0 条评论 / 1329浏览

最近工作的产品需求上要开发一个人脸图片上传的接口,于是就有了下面的再正常不过的代码

    @ApiOperation("xxx数据采集-人脸相片上传")
    @PostMapping("/xxxn/face-upload")
    public RespResult<Void> cashLoanFaceUpload(@RequestPart MultipartFile file,
                                               @RequestParam("applyId") String applyId,
                                               @RequestParam("memberId") String memberId)
    {
        return cashLoanService.cashLoanCheckAndFaceUpload(file,applyId,memberId);
    }

写完之后准备测一下接口,但是第一次调用就出现了下面的报错:

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
    ...
at com.google.gson.Gson$FutureTypeAdapter.write(Gson.java:977)
    ...

刚开始我以为代码有问题,但是看了下前面的类似的文件操作的接口,一模一样的格式,就是参数名方法名不同,如下:

    @PostMapping("/xxx/upload")
    public RespResult<Void> devicePriceUpload(@RequestParam("file") MultipartFile file) throws IOException {
        return okCardWebService.devicePriceUpload(file);
    }

但是上面这个是可以的,不会报那个栈溢出的异常.我对比了一下,这TM唯一的区别就是我的接口上加了@ApiOperation("xxx数据采集-人脸相片上传"),此时我恍然大悟,因为我们的项目中有记录出入参的切面,刚来公司的时候我就看了下实现,逻辑很简单,就是普通的aop记录入参日志,是按照@ApiOperation或者一个自定义的注解@Log来决定是否要记录入参日志 ,主要代码如下

ApiOperation api = method.getAnnotation(ApiOperation.class);
        Log log = method.getAnnotation(Log.class);
        if (log != null){
            if (!log.enable()){
                return point.proceed();
            }
        }
        List<String> params = Arrays.stream(args)
                .filter(arg->!(arg instanceof HttpServletRequest || arg instanceof HttpServletResponse))
                .map(arg -> GsonUtils.toJson(arg))
                .collect(Collectors.toList());

显然我这里的接口是会记录的了,再根据栈溢出后打印的调用栈都是gson,基本可以确定就是记录入参MultipartFile的时候发生的了,查询资料(百度谷歌啦^.^)后发现GSON不支持处理具有循环引用的对象的序列化,之后我实际重现了先,发现的确如此.

比如下面的两个相互引用的对象

@Data
public class A {
	private String name;
	private B b;

	public A(String name) {
		this.b = new B(this);
		this.name = name;
	}
}

@Data
public class B {
	private A a;

	public B(A a) {
		this.a = a;
	}
}

gson无法序列化循环引用对象

可以看到new出来的对象,在调用构造方法时就会出现循环的问题导致栈溢出,直接使用GSON序列化为json字符串就会出现栈溢出,但是可以使用fastJson序列化,fastJson支持循环引用的对象序列化,结果如下

{
    "b":{
        "a":{
            "$ref":".."
        }
    },
    "name":"A"
}

看到上面的结果,其实也解了我在上一个公司工作的时候遇到的一个问题,当时打印出来的日志就是很多$ref,但是因为问题不大,也比较忙当时没有深究,现在恍然大悟.

所以要解决上面的问题,有很多方法了.

  1. 不用Gson,改用fastJson,但这属于基础sdk的部分我无法修改
  2. 在filter中过滤MultipartFile这种类型的入参,个人觉得这是最合理的,可以记录文件名字或一些基础文件信息,但是这属于sdk的范围,我目前无法修改
  3. 那就不打印入参日志就好了,直接去掉@ApiOperation
  4. 但是去掉不好,接口文档也没有了,但是按照那个sdk的切面逻辑,可以加上@Log(enable=false)也可以保证不打印入参
  5. 但是这样,也不大好,导致我后面两个id的参数也记录不了,但没关系,我自己在代码里面记录吧