Loading [MathJax]/jax/output/CommonHTML/jax.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >浅析 SpringMVC 中返回对象的循环引用问题

浅析 SpringMVC 中返回对象的循环引用问题

作者头像
kirito-moe
发布于 2021-07-16 03:23:44
发布于 2021-07-16 03:23:44
6.1K00
代码可运行
举报
运行总次数:0
代码可运行

「技术分享」某种程度上,是让作者和读者,不那么孤独的东西。欢迎关注我的微信公众号:「Kirito的技术分享」

问题发现

今天这个话题还是比较轻松的,可能很多朋友也都遇到过这个问题。

@RestController、@ResponseBody 等注解是我们在写 Web 应用时打交道最多的注解了,我们经常有这样的需求:返回一个对象给前端,SpringMVC 帮助我们序列化成 JSON 对象。而今天我要分享的话题也不是什么高深的内容,那就是返回对象中存在循环引用时问题的探讨。

该问题非常简单容易复现,直接上代码。

准备两个存在循环引用的对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Data
public class Person {
    private String name;
    private IdCard idCard;
}

@Data
public class IdCard {
    private String id;
    private Person person;
}

在 SpringMVC 的 controller 中直接返回存在循环引用的对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestController
public class HelloController {

    @RequestMapping("/hello")
    public Person hello() {
        Person person = new Person();
        person.setName("kirito");

        IdCard idCard = new IdCard();
        idCard.setId("xxx19950102xxx");

        person.setIdCard(idCard);
        idCard.setPerson(person);

        return person;
    }
}

执行 curl localhost:8080/hello 发现,直接报了一个 StackOverFlowError:

StackOverFlow

问题剖析

不难理解这中间发生了什么,从堆栈和常识中都应当了解到一个事实,SpringMVC 默认使用了 jackson 作为 HttpMessageConverter,这样当我们返回对象时,会经过 jackson 的 serializer 序列化成 json 串,而另一个事实便是 jackson 是无法解析 java 中的循环引用的,套娃式的解析,最终导致了 StackOverFlowError。

有人会说,为什么你会有循环引用呢?天知道业务场景有多奇葩,既然 Java 没有限制循环引用的存在,那就肯定会有某一合理的场景存在该可能性,如果你在线上的一个接口一直平稳运行着,知道有一天,碰到了一个包含循环引用的对象,你看着打印出来的 StackOverFlowError 的堆栈,开始怀疑人生,是哪个小(大)可(S)爱(B)干的这种事!

我们先假设循环引用存在的合理性,如何解决该问题呢?最简单的解法:单向维护关联,参考 Hibernate 中的 OneToMany 关联中单向映射的思想,这需要干掉 IdCard 中的 Person 成员变量。或者,借助于 jackson 提供的注解,指定忽略循环引用的字段,例如这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Data
public class IdCard {
    private String id;
    @JsonIgnore
    private Person person;
}

当然,我也翻阅了一些资料,尝试寻求 jackson 更优雅的解决方式,例如这两个注解:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@JsonManagedReference
@JsonBackReference

但在我看来,似乎他们并没有什么大用场。

当然,你如果不嫌弃经常出安全漏洞的 fastjson,也可以选择使用 FastJsonHttpMessageConverter 替换掉 jackson 的默认实现,像下面这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
    //1、定义一个convert转换消息的对象
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

    //2、添加fastjson的配置信息
    FastJsonConfig fastJsonConfig = new FastJsonConfig();

    SerializerFeature[] serializerFeatures = new SerializerFeature[]{
        //    输出key是包含双引号
        //                SerializerFeature.QuoteFieldNames,
        //    是否输出为null的字段,若为null 则显示该字段
        //                SerializerFeature.WriteMapNullValue,
        //    数值字段如果为null,则输出为0
        SerializerFeature.WriteNullNumberAsZero,
        //     List字段如果为null,输出为[],而非null
        SerializerFeature.WriteNullListAsEmpty,
        //    字符类型字段如果为null,输出为"",而非null
        SerializerFeature.WriteNullStringAsEmpty,
        //    Boolean字段如果为null,输出为false,而非null
        SerializerFeature.WriteNullBooleanAsFalse,
        //    Date的日期转换器
        SerializerFeature.WriteDateUseDateFormat,
        //    循环引用
        //SerializerFeature.DisableCircularReferenceDetect,
    };

    fastJsonConfig.setSerializerFeatures(serializerFeatures);
    fastJsonConfig.setCharset(Charset.forName("UTF-8"));

    //3、在convert中添加配置信息
    fastConverter.setFastJsonConfig(fastJsonConfig);

    //4、将convert添加到converters中
    HttpMessageConverter<?> converter = fastConverter;

    return new HttpMessageConverters(converter);
}

你可以自定义一些 json 转换时的 feature,当然我今天主要关注 SerializerFeature.DisableCircularReferenceDetect 这一属性,只要不显示开启该特性,fastjson 默认就能处理循环引用的问题。

如上配置后,让我们看看效果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{"idCard":{"id":"xxx19950102xxx","person":{"$ref":".."}},"name":"kirito"}

已经正常返回了,fastjson 使用了"$ref":".." 这样的标识,解决了循环引用的问题,如果继续使用 fastjson 反序列化,依旧可以解析成同一对象,其实我在之前的文章中已经介绍过这一特性了《gson 替换 fastjson 引发的线上问题分析》。

使用 FastJsonHttpMessageConverter 可以彻底规避掉循环引用的问题,这对于返回类型不固定的场景十分有帮助,而 @JsonIgnore 只能作用于那些固定结构的循环引用对象上。

问题思考

值得一提的是,为什么一般标准的 JSON 类库并没有如此关注循环引用的问题呢?fastjson 看起来反而是个特例,我觉得主要还是 JSON 这种序列化的格式就是为了通用而存在的,ref 在序列化、反序列化时能够正常解析,但如果是跨框架、跨系统、跨语言等场景,这一切都是个未知数了。说到底,这还是 Java 语言的循环引用和 JSON 通用规范不包含这一概念之间的 gap(可能 JSON 规范描述了这一特性,但我没有找到,如有问题,烦请指正)。

我到底应该选择 @JsonIgnore 还是使用 FastJsonHttpMessageConverter 呢?经历了上面的思考,我觉得各位看官应该能够根据自己的场景选择合适的方案了。

总结下,如果选择 FastJsonHttpMessageConverter ,改动较大,如果有较多的存量接口,建议做好回归,以确认解决循环引用问题的同时,别引入了其他不兼容的改动。并且,需要基于你的使用场景评估方案,如果出现了循环引用,fastjson 会使用 $ref 来记录引用信息,请确认你的前端或者接口方能够识别该信息,因为这可能并不是标准的 JSON 规范。你也可以选择 @JsonIgnore 来实现最小改动,但也同时需要注意,如果根据序列化的结果再次反序列化,引用信息可不会自动恢复。

- END -

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kirito的技术分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Spring Boot 2.0 + FastJson 1.2.+作为JSON序列化
SpringBoot配置FastJson的时候,报错: java.lang.IllegalArgumentException: Content-Type cannot contain wildcard type '*' at org.springframework.util.Assert.isTrue(Assert.java:116) ~[spring-core-5.0.13.RELEASE.jar!/:5.0.13.RELEASE] at org.springframework.http.HttpHe
干货满满张哈希
2021/04/12
9860
java responsebody_SpringBoot ResponseBody返回值处理的实现「建议收藏」
@postmapping(path = “/test”, produces = mediatype.application_json_value)
全栈程序员站长
2022/09/02
8540
json日期格式化与json日期转化为对象
参考:https://blog.csdn.net/qq_28929589/article/details/79245774
CBeann
2023/12/25
5530
json日期格式化与json日期转化为对象
spring cloud 学习(11) - 用fastson替换jackson及用gb2312码输出
前几天遇到一个需求,因为要兼容旧项目的编码格式,需要spring-cloud的rest接口,输出gb2312编码,本以为是一个很容易的事情,比如下面这样:
菩提树下的杨过
2018/09/20
1.4K0
spring cloud 学习(11) - 用fastson替换jackson及用gb2312码输出
fastjson全局序列化坑
今天遇到这样一个问题: 序列化出现了与预期不一致的效果,重现代码很简单,就返回一个list,包含几个对象
阿超
2022/08/17
1.2K0
fastjson全局序列化坑
修复Long类型太长,而Java序列化JSON丢失精度问题的方法
Java序列化JSON时long型数值,会出现精度丢失的问题。  原因:  java中得long能表示的范围比js中number大,也就意味着部分数值在js中存不下(变成不准确的值).  解决办法(一):  使用ToStringSerializer的注解,让系统序列化  时,保留相关精度     @JsonSerialize(using=ToStringSerializer.class)     private Long createdBy; 上述方法需要在每个对象都配上该注解,此方法过于繁锁。 解决办
似水的流年
2019/12/06
2K0
引入FastJsonHttpMessageConverter需要注意的地方
FastJsonHttpMessageConverter是基于fastjson的一种HttpMessageConverter,spring系统默认使用的是MappingJackson2HttpMessageConverter,但是在使用FastJsonHttpMessageConverter时要特别注意,因为FastJsonHttpMessageConverter很可能就会处理字符串类型,这样就可能会导致字符串在被fastjson序列化时出现转义字符,这样到了服务提供端就会出现无法解析的问题,但是fastjson又没有提供一个序列化特性:不序列化字符串,解决这个问题有两种解决方案:
johnhuster的分享
2022/03/28
2.1K0
献给移动端的服务器搭建
application.properties这个是项目的一些配置,举例一下默认是8080端口,我们如果想改下端口的话,就可以在配置增加
Dwyane
2018/12/12
1.2K0
Spring Boot 自定义Spring MVC 配置: WebMvcConfigurationSupport
Spring Boot 自定义Spring MVC 配置: WebMvcConfigurationSupport
一个会写诗的程序员
2018/08/17
7130
深入探索Spring Boot基础功能(二):JSON数据处理与日志记录
大家好,我是默语,一个热爱技术分享的博主。今天我们将深入探讨Spring Boot的基础功能,包括如何处理JSON数据和使用slf4j进行日志记录。这篇博客将详细介绍Spring Boot在这些方面的强大能力,通过实际代码案例演示,为大家提供实用的开发技巧。希望这篇文章能为你的Spring Boot学习之旅提供有价值的参考。✨
默 语
2024/11/20
1970
03 FastJson 解决循环引用
用户7630333
2023/12/07
2570
03 FastJson 解决循环引用
SpringBoot配置FastJson中存在的乱码问题
之前没有使用过SpringBoot,现在这个项目中有使用,刚好项目赶的差不多了,今天就想好好学学,解决遇到各种BUG的不断挑战。
一个程序员的成长
2020/11/25
1.4K0
SpringBoot配置FastJson中存在的乱码问题
fastjson 重复引用和循环引用问题
数据传输使用json格式再方便不过了。 fastjson 由阿里巴巴那伙人使用Java语言编写,号称最快的JSON库 前两天遇到一个问题 后台的数据转化为json字符串后发送到前台出现了$ref字样的东西,后来明白了这是引用,在传输的数据中出现相同的对象时,fastjson默认开启引用检测将相同的对象写成引用的形式. 说到引用分为两种,重复引用和循环引用
Mshu
2018/10/31
2.7K0
Spring Boot+Gradle+ MyBatisPlus3.x搭建企业级的后台分离框架
阿里JSON解析器,详细文档请看官方 https://github.com/alibaba/fastjson
后端码匠
2020/09/07
6190
Spring Boot+Gradle+ MyBatisPlus3.x搭建企业级的后台分离框架
前后端分离使用Jackson或者fastjson解决后端忽略实体类中的某个属性不返回给前端的方法
接收到的需求:我们前端只需要id、name、gender,phone不需要给前端。 一开始想法直接重新写一个VO,属性里去掉phone,这样一下多了个文件,显然不是我们想要的!接下来教你两种方式实现一下哦!!
掉发的小王
2022/07/11
2.6K0
前后端分离使用Jackson或者fastjson解决后端忽略实体类中的某个属性不返回给前端的方法
fastjson 笔记
demo/fastjson at master · suveng/demo · GitHub
suveng
2019/11/12
1.6K0
记一次参数走私导致的权限绕过
朋友们现在只对常读和星标的公众号才展示大图推送,建议大家把“亿人安全“设为星标”,否则可能就看不到了啦
亿人安全
2024/04/26
2090
记一次参数走私导致的权限绕过
Java进阶|Springboot切换fastjson序列化实战
在SpringBoot中,默认情况下使用的是Jackson作为JSON的序列化和反序列化库。但有时候,我们可能需要切换到其他的JSON库,比如Fastjson。Fastjson是阿里巴巴的一个开源项目,它提供了高性能的JSON序列化和反序列化功能。
六月暴雪飞梨花
2024/01/28
1.8K1
Java进阶|Springboot切换fastjson序列化实战
SpringBoot从1.5.4升级到2.7.2问题总结
编译不报错,启动报错,在springboot1.3版本中会默认提供一个RestTemplate的实例Bean,当在springboot1.4以及以后的版本中,需要手动创建一个RestTemplate的配置,这里将会导致循环依赖
猫头虎
2024/04/08
5450
SpringBoot从1.5.4升级到2.7.2问题总结
SpringBoot 返回 json 数据以及数据封装(万字长文)
在项目开发中,接口与接口之间,前后端之间数据的传输都使用 Json 格式,在 Spring Boot 中,接口返回 Json 格式的数据很简单,在 Controller 中使用@RestController注解即可返回 Json 格式的数据,@RestController也是 Spring Boot 新增的一个注解,我们点进去看一下该注解都包含了哪些东西。
技术从心
2020/07/22
6.6K0
推荐阅读
相关推荐
Spring Boot 2.0 + FastJson 1.2.+作为JSON序列化
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验