发现问题
今天的话题还很轻松,但是很多朋友可能遇到过这个问题。
@RestController、@ResponseBody等评论是我们在编写web APP应用程序时交流最多的评论,它将对象返回到前端,SpringMVC将对象串联到JSON对象今天我要分享的话题也不是高级内容。 那是对返回对象有循环参照时的问题的讨论。
这个问题非常简单,容易再现,直接上码。
准备两个存在循环引用的对象。
@ datapublicclassperson { privatestring name; 私有身份卡; } @ datapublicclassidcard {专用testring id; 隐私人员; }在SpringMVC的控制器中直接返回循环引用所在的对象:
@ restcontrollerpublicclasshellocontroller { @ request mapping ('/hello ' ) publicPersonhello ) { person person=new person pello IdCardidCard=newIdCard (; idcard.setid(XXX19950102XXX ); Person.setidcard(idcard ); idcard.setPerson(Person ); 返回人员; 进行了curl localhost:8080/hello的发现,直接报告了堆栈溢出错误:
通过堆栈溢出问题的分析,可以很容易地理解这中间发生了什么。 你应该从堆栈和常识两方面了解一个事实。 默认情况下,SpringMVC使用冷漠的眼睛作为HttpMessageConverter。 这样,当我们返回对象时,经过冷眼的serializer就会序列化为json字符串。 另一个事实是,寒冷的眼睛无法分析java的循环引用。 伊娃式的分析最终导致了堆栈溢出错误。
为什么会有循环引用呢? 我知道商业场景有多奇怪。 既然Java中不限制循环引用的存在,那么一个合理的场景就一定有这种可能性。 如果你知道线上的一个界面一直运行顺畅,有一天遇到了包含循环引用的对象,你会看到打印的堆栈溢出错误堆栈,开始怀疑人生,有多小
首先假设循环引用存在的合理性,如何解决这个问题呢? 最简单的解法:单向保持关联,借鉴Hibernate的OneToMany相关单向映射的思想。 这需要杀死IdCard的人员成员变量。 或者,使用冷眼睛提供的注释指定要忽略循环引用的字段。 例如,以下情况:
@ datapublicclassidcard {专用testring id; @JsonIgnoreprivatePersonperson; }当然,我也读了一些资料,试着寻求冷眼更优雅的解决方法。 例如,这两个评论:
@ jsonmanagedreference @ JSON back reference,但对我来说他们好像没什么用。
当然,除了讨厌经常出现安全漏洞的fastjson之外,还可以用FastJsonHttpMessageConverter替换冷眼缺省实现,如下所示:
@ beanpublichttpmessageconvertersfastjsonhttpmessageconverters ((/1,要为其定义转换消息的fastjsonhttpmessageconverterfastters 串行链接
zerFeature[] 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 默认就能处理循环引用的问题。
如上配置后,让我们看看效果:
{"idCard":{"id":"xxx19950102xxx","person":{"$ref":".."}},"name":"kirito"}已经正常返回了,fastjson 使用了"$ref":".." 这样的标识,解决了循环引用的问题,如果继续使用 fastjson 反序列化,依旧可以解析成同一对象,其实我在之前的文章中已经介绍过这一特性了《gson 替换 fastjson 引发的线上问题分析》。
使用 FastJsonHttpMessageConverter 可以彻底规避掉循环引用的问题,这对于返回类型不固定的场景十分有帮助,而 @JsonIgnore 只能作用于那些固定结构的循环引用对象上。
问题思考值得一提的是,为什么一般标准的 JSON 类库并没有如此关注循环引用的问题呢?fastjson 看起来反而是个特例,我觉得主要还是 JSON 这种序列化的格式就是为了通用而存在的,$ref 这样的契约信息,并没有被 JSON 的规范去定义,fastjson 可以确保 $ref 在序列化、反序列化时能够正常解析,但如果是跨框架、跨系统、跨语言等场景,这一切都是个未知数了。说到底,这还是 Java 语言的循环引用和 JSON 通用规范不包含这一概念之间的 gap(可能 JSON 规范描述了这一特性,但我没有找到,如有问题,烦请指正)。
我到底应该选择 @JsonIgnore 还是使用 FastJsonHttpMessageConverter 呢?经历了上面的思考,我觉得各位看官应该能够根据自己的场景选择合适的方案了。
总结下,如果选择 FastJsonHttpMessageConverter ,改动较大,如果有较多的存量接口,建议做好回归,以确认解决循环引用问题的同时,别引入了其他不兼容的改动。并且,需要基于你的使用场景评估方案,如果出现了循环引用,fastjson 会使用 $ref 来记录引用信息,请确认你的前端或者接口方能够识别该信息,因为这可能并不是标准的 JSON 规范。你也可以选择 @JsonIgnore 来实现最小改动,但也同时需要注意,如果根据序列化的结果再次反序列化,引用信息可不会自动恢复。
热门内容:求你别再用swagger了,给你推荐几个在线文档生成神器IntelliJ IDEA官方宣布中文汉化包正式发布低代码 yyds再见Spring!下一个开源框架更香!最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。明天见(。・ω・。)ノ♡