参考文章
https://www.jianshu.com/p/6041242405e8
现象
最近项目开发中,遇到 Service 接口返回数据正常,经过API层后返回给端上的数据出现了 $ref: $.list[1]
,
导致端上数据解析异常。
问题原因
出现这种问题的原因是项目 API 层在给端上数据的时候会使用FastJSON 将对象转为 JSON 字符串,在解析对象的时候为了避免因对象的重复引用出现栈溢出异常,会对对象进行循环引用检测,默认如果重用对象的话,会使用引用的方式进行引用对象。因 Service 接口返回的数据中有对象被重复引用,因此出现该问题。
何为重复/循环引用
简单说,重复引用就是一个集合/对象中的多个元素/属性同时引用同一对象,循环引用就是集合/对象中的多个元素/属性存在相互引用导致循环。
重复引用
@Test
public void testFastJSON(){
List<Object> list = Lists.newArrayList();
Object obj = new Object();
list.add(obj);
list.add(obj);
String s = JSON.toJSONString(list);
System.out.println(s);
}
输出结果:
[{},{"$ref":"$[0]"}]
循环引用
// 循环引用的特殊情况,自引用
Map<String, Object> map = new HashMap<>();
map.put("map", map);
System.out.println("自引用 Map:" + JSON.toJSONString(map));
// map1引用了map2,而map2又引用map1,导致循环引用
Map<String, Object> map1 = new HashMap<>();
Map<String, Object> map2 = new HashMap<>();
map1.put("map", map2);
map2.put("map", map1);
System.out.println("循环引用Map1: " + JSON.toJSONString(map1));
System.out.println("循环引用Map2: " + JSON.toJSONString(map2));
输出结果:
自引用 Map:{"map":{"$ref":"@"}}
循环引用Map1: {"map":{"map":{"$ref":".."}}}
循环引用Map2: {"map":{"map":{"$ref":".."}}}
一般来说,存在循环引用问题的集合/对象在序列化时(比如Json化),如果不加以处理,会触发StackOverflowError异常。
分析原因:当序列化引擎解析map1时,它发现这个对象持有一个map2的引用,转而去解析map2。解析map2时,发现他又持有map1的引用,又转回map1。如此产生StackOverflowError异常。
引用标识说明:
“$ref”:”..” 上一级
“$ref”:”@” 当前对象,也就是自引用
“$ref”:”$” 根对象
{"$ref":"../.."} 引用父对象的父对象
“$ref”:”$.children.0” 基于路径的引用,相当于root.getChildren().get(0)
解决方法
临时解决方案(局部)
该 API 接口返回数据时进行特殊处理:
String s = JSON.toJSONString(re1, SerializerFeature.DisableCircularReferenceDetect);
re1 = JSON.parseObject(s,Result.class);
Spring 项目(全局)
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<array>
<value>text/html;charset=UTF-8</value>
</array>
</property>
<property name="features">
<array>
<value>WriteMapNullValue</value>
<value>WriteNullStringAsEmpty</value>
<!-- 全局关闭循环引用检查,最好是不要关闭,不然有可能会StackOverflowException -->
<value>DisableCircularReferenceDetect</value>
</array>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
SpringBoot 项目
public class FastJsonHttpMessageConverterEx extends FastJsonHttpMessageConverter{
public FastJsonHttpMessageConverterEx(){
//在这里配置fastjson特性(全局设置的)
FastJsonConfig fastJsonConfig = new FastJsonConfig();
//fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); //自定义时间格式
//fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue); //正常转换null值
//fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); //关闭循环引用
this.setFastJsonConfig(fastJsonConfig);
}
@Override
protected boolean supports(Class<?> clazz) {
return super.supports(clazz);
}
}
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
.....
@Bean
public FastJsonHttpMessageConverterEx fastJsonHttpMessageConverterEx(){
return new FastJsonHttpMessageConverterEx();
}
}