最近做一个需求,大致是这样的,从外部服务获取数据,然后处理后存入缓存中。然后遇到一个奇怪的问题,从缓存数据库中取出来的数据是这样的
图片上展示的是从缓存数据库取出的数据,部分数据是正常显示,部分数据是非正常显示,
虽然是乱码,但看起来是有规律的——都是以“$ref” 打头的,很像是使用了引用,所以初步
猜测这是一种引用的存储方式,【类似于java虚拟机中对象的地址引用】属于Hive的一种存
储策略,询问Hive相关大佬,被告知并不是。
然后debug发现其实实在入库之前就是这种样式了,所以很快定位到是在使用FastJson对对象
转String时出现的问题。
这里时复现代码
package forry.less.versions.wappers;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.ArrayList;
import java.util.List;
public class TestFastJson {
public static void main(String[] args) {
Person person = new Person();
person.setName("abc");
person.setAge("19");
List<Person> lists = new ArrayList<>();
lists.add(person);
lists.add(person);
String s = JSON.toJSONString(lists);
}
private static class Person {
private String name;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
private Person person;
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
运行结果s: [{"age":"19","name":"abc"},{"$ref":"$[0]"}]
查阅了Fastjson关于序列化的资料,关于产生这个的原因是这样的,FastJson在序列化的时候
在fastjson中进行对象转 String 的方法有多个重载方法
public static java.lang.String toJSONString(Object object) { /* compiled code */ }
public static java.lang.String toJSONString(Object object, SerializerFeature... features)
{ /* compiled code */ }
public static java.lang.String toJSONString(Object object, int defaultFeatures,
SerializerFeature... features) { /* compiled code */ }
一般我们会使用第一个重载方法,这个重载方法只有一个参数——被序列化的对象,当使用这个
方法时Fastjson会给SerializerFeature一个默认的值,如果使用默认值,那么在后续的序列化过程
中就会在上下文去检查引用,如果要序列化的子对象在上文已经序列化了,那么就不会继续序列化
该对象,而是使用上一个已经被序列化的子对象的地址,如"$ref":"$[0]" 代表当前对象使用
集合中第一个对象的序列化值——"age":"19","name":"abc",减小了序列化后字符串的大小。
如果说不想序列化后的字符串存在引用的格式的话可以使用另外的重载方法,另外的重载方法其中
一个参数SerializerFeature的值设置为 SerializerFeature.DisableCircularReferenceDetect,【它的
作用时消除引用,即,在序列化时不会去检查上下文的引用,全部扁平化】
Person person = new Person();
person.setName("abc");
person.setAge("19");
//person.setPerson(person);
List<Person> lists = new ArrayList<>();
lists.add(person);
lists.add(person);
String s =
JSON.toJSONString(lists,SerializerFeature.DisableCircularReferenceDetect);
运行结果s: [{"age":"19","name":"abc"},{"age":"19","name":"abc"}]
注意,SerializerFeature.DisableCircularReferenceDetect 这个枚举要慎用,因为这个枚
举消除了引用,那么当我要序列化的对象中存在循环引用时会出现栈溢出ERROR
Person person = new Person();
person.setName("abc");
person.setAge("19");
person.setPerson(person);//这里存在循环引用
String s =
JSON.toJSONString(person,SerializerFeature.DisableCircularReferenceDetect);
运行结果:
Exception in thread "main" java.lang.StackOverflowError
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.alibaba.fastjson.util.FieldInfo.get(FieldInfo.java:484)
at com.alibaba.fastjson.serializer.FieldSerializer.getPropertyValueDirect(FieldSerializer.java:140)
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:249)
at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:120)
at com.alibaba.fastjson.serializer.FieldSerializer.writeValue(FieldSerializer.java:298)
值得指出的是,虽然我们使用了Fastjson的单参数序列化方法导致了序列化后的字符串中存在地址
引用,但是当我使用Fastjson反序列化后获得的对象任然可以获得一个数据正常的对象。