筹备两个存在循环援用的对象:
@Data
public class Person {
private String name;private IdCard idCard;
}
@Data
public class IdCard {
private String id;private Person person;
}
在 SpringMVC 的 controller 中间接返回存在循环援用的对象:
@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:
问题分析
不难理解这两头产生了什么,从堆栈和常识中都该当理解到一个事实,SpringMVC 默认应用了 jackson 作为 HttpMessageConverter,这样当咱们返回对象时,会通过 jackson 的 serializer 序列化成 json 串,java培训而另一个事实便是 jackson 是无奈解析 java 中的循环援用的,套娃式的解析,最终导致了 StackOverFlowError。
有人会说,为什么你会有循环援用呢?天知道业务场景有多奇葩,既然 Java 没有限度循环援用的存在,那就必定会有某一正当的场景存在该可能性,如果你在线上的一个接口始终安稳运行着,晓得有一天,碰到了一个蕴含循环援用的对象,你看着打印进去的 StackOverFlowError 的堆栈,开始狐疑人生,是哪个小(大)可(S)爱(B)干的这种事!
咱们先假如循环援用存在的合理性,如何解决该问题呢?最简略的解法:单向保护关联,参考 Hibernate 中的 OneToMany 关联中单向映射的思维,这须要干掉 IdCard 中的 Person 成员变量。或者,借助于 jackson 提供的注解,指定疏忽循环援用的字段,例如这样:
@Data
public class IdCard {
private String id;@JsonIgnoreprivate Person person;
}
当然,我也翻阅了一些材料,尝试寻求 jackson 更优雅的解决形式,例如这两个注解:
@JsonManagedReference
@JsonBackReference
但在我看来,仿佛他们并没有什么大用场。
当然,你如果不厌弃常常出安全漏洞的 fastjson,也能够抉择应用 FastJsonHttpMessageConverter 替换掉 jackson 的默认实现,像上面这样:
@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 默认就能解决循环援用的问题。
如上配置后,让咱们看看成果:
{"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 来实现最小改变,但也同时须要留神,如果依据序列化的后果再次反序列化,援用信息可不会主动复原。