首页 > 编程知识 正文

stackoverflowerror原因(牛逼的SpringMVC居然出现了StackOverFlowError?)

时间:2023-05-05 23:46:09 阅读:123734 作者:1349

发现问题

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

@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 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。