我正在编写一个序列化程序以将POJO序列化为JSON,但陷入循环引用问题。在休眠的双向一对多关系中,父级引用子级,而子级引用又回到父级,此时我的序列化程序死亡。(请参见下面的示例代码)
如何打破这个循环?我们可以获取对象的所有者树以查看对象本身是否存在于其所有者层次结构中的某个位置吗?还有其他方法可以找到参考是否为圆形吗?或任何其他想法来解决这个问题?
我正在编写一个序列化程序以将POJO序列化为JSON,但陷入循环引用问题。在休眠的双向一对多关系中,父级引用子级,而子级引用又回到父级,此时我的序列化程序死亡。(请参见下面的示例代码)
如何打破这个循环?我们可以获取对象的所有者树以查看对象本身是否存在于其所有者层次结构中的某个位置吗?还有其他方法可以找到参考是否为圆形吗?或任何其他想法来解决这个问题?
Answers:
双向关系甚至可以用JSON表示吗?某些数据格式不适用于某些类型的数据建模。
处理遍历对象图时处理周期的一种方法是跟踪您到目前为止已看到的对象(使用身份比较),以防止自己遍历无限循环。
我靠 Google JSON通过使用The feature处理此类问题
假设A类和B类之间的双向关系如下
public class A implements Serializable {
private B b;
}
和B
public class B implements Serializable {
private A a;
}
现在使用GsonBuilder如下获取自定义Gson对象(注意setExclusionStrategies方法)
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
public boolean shouldSkipClass(Class<?> clazz) {
return (clazz == B.class);
}
/**
* Custom field exclusion goes here
*/
public boolean shouldSkipField(FieldAttributes f) {
return false;
}
})
/**
* Use serializeNulls method if you want To serialize null values
* By default, Gson does not serialize null values
*/
.serializeNulls()
.create();
现在我们的循环参考
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
String json = gson.toJson(a);
System.out.println(json);
看看GsonBuilder类
Jackson 1.6(于2010年9月发布)具有基于注释的特定支持,可以处理此类父/子链接,请参阅http://wiki.fasterxml.com/JacksonFeatureBiDirReferences。(回溯快照)
您当然可以使用大多数JSON处理程序包(已经至少支持jackson,gson和flex-json)来排除父链接的序列化,但是真正的窍门在于如何反序列化它(重新创建父链接),只需处理序列化方面。尽管现在看来,排除可能对您有用。
编辑(2012年4月):杰克逊2.0现在支持真实的身份引用(Wayback Snapshot),因此您也可以通过这种方式解决它。
为了解决这个问题,我采用了以下方法(对我的应用程序进行标准化处理,使代码清晰可重用):
这是代码:
1)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface GsonExclude {
}
2)
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
public class GsonExclusionStrategy implements ExclusionStrategy{
private final Class<?> typeToExclude;
public GsonExclusionStrategy(Class<?> clazz){
this.typeToExclude = clazz;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return ( this.typeToExclude != null && this.typeToExclude == clazz )
|| clazz.getAnnotation(GsonExclude.class) != null;
}
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(GsonExclude.class) != null;
}
}
3)
static Gson createGsonFromBuilder( ExclusionStrategy exs ){
GsonBuilder gsonbuilder = new GsonBuilder();
gsonbuilder.setExclusionStrategies(exs);
return gsonbuilder.serializeNulls().create();
}
4)
public class MyObjectToBeSerialized implements Serializable{
private static final long serialVersionID = 123L;
Integer serializeThis;
String serializeThisToo;
Date optionalSerialize;
@GsonExclude
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="refobj_id", insertable=false, updatable=false, nullable=false)
private MyObjectThatGetsCircular dontSerializeMe;
...GETTERS AND SETTERS...
}
5)
在第一种情况下,为构造函数提供了null,您可以指定另一个要排除的类-两个选项都添加在下面
Gson gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(null) );
Gson _gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(Date.class) );
6)
MyObjectToBeSerialized _myobject = someMethodThatGetsMyObject();
String jsonRepresentation = gsonObj.toJson(_myobject);
或者,排除Date对象
String jsonRepresentation = _gsonObj.toJson(_myobject);
如果您正在使用Jackon进行序列化,只需将@JsonBackReference应用于双向映射即可解决循环引用问题。
注意:@JsonBackReference用于解决无限递归(StackOverflowError)
@JsonIgnore
使我JpaRepository
无法映射该属性,但@JsonBackReference
解决了循环引用问题,并且仍然允许对有问题的属性进行适当的映射
如果您使用的是Javascript,则可以使用replacer
参数JSON.stringify()
method,您可以在其中传递函数来修改默认的序列化行为。
使用方法如下。考虑下面的示例,在循环图中有4个节点。
// node constructor
function Node(key, value) {
this.name = key;
this.value = value;
this.next = null;
}
//create some nodes
var n1 = new Node("A", 1);
var n2 = new Node("B", 2);
var n3 = new Node("C", 3);
var n4 = new Node("D", 4);
// setup some cyclic references
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n1;
function normalStringify(jsonObject) {
// this will generate an error when trying to serialize
// an object with cyclic references
console.log(JSON.stringify(jsonObject));
}
function cyclicStringify(jsonObject) {
// this will successfully serialize objects with cyclic
// references by supplying @name for an object already
// serialized instead of passing the actual object again,
// thus breaking the vicious circle :)
var alreadyVisited = [];
var serializedData = JSON.stringify(jsonObject, function(key, value) {
if (typeof value == "object") {
if (alreadyVisited.indexOf(value.name) >= 0) {
// do something other that putting the reference, like
// putting some name that you can use to build the
// reference again later, for eg.
return "@" + value.name;
}
alreadyVisited.push(value.name);
}
return value;
});
console.log(serializedData);
}
稍后,您可以通过分析序列化的数据并修改next
属性以指向实际对象(如果@
在此示例中使用带有类似符号的命名引用),轻松地使用循环引用重新创建实际对象。
这就是我最终解决此问题的方式。至少与Gson&Jackson合作。
private static final Gson gson = buildGson();
private static Gson buildGson() {
return new GsonBuilder().addSerializationExclusionStrategy( getExclusionStrategy() ).create();
}
private static ExclusionStrategy getExclusionStrategy() {
ExclusionStrategy exlStrategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes fas) {
return ( null != fas.getAnnotation(ManyToOne.class) );
}
@Override
public boolean shouldSkipClass(Class<?> classO) {
return ( null != classO.getAnnotation(ManyToOne.class) );
}
};
return exlStrategy;
}
如果您使用的是Spring Boot,Jackson在根据循环/双向数据创建响应时会抛出错误,因此请使用
@JsonIgnoreProperties
忽略圆度
At Parent:
@OneToMany(mappedBy="dbApp")
@JsonIgnoreProperties("dbApp")
private Set<DBQuery> queries;
At child:
@ManyToOne
@JoinColumn(name = "db_app_id")
@JsonIgnoreProperties("queries")
private DBApp dbApp;
当您有两个对象时,可能会出现此错误:
class object1{
private object2 o2;
}
class object2{
private object1 o1;
}
使用GSon进行序列化时,出现了以下错误:
java.lang.IllegalStateException: circular reference error
Offending field: o1
要解决这个问题,只需添加关键字transient:
class object1{
private object2 o2;
}
class object2{
transient private object1 o1;
}
如您在这里所看到的:为什么Java具有瞬态字段?
Java中的瞬态关键字用于指示不应序列化字段。
Jackson提供JsonIdentityInfo
注释以防止循环引用。您可以在此处查看教程。
我认为答案数字8更好,因此,如果您知道哪个字段引发了错误,则只需将fild设置为null即可解决。
List<RequestMessage> requestMessages = lazyLoadPaginated(first, pageSize, sortField, sortOrder, filters, joinWith);
for (RequestMessage requestMessage : requestMessages) {
Hibernate.initialize(requestMessage.getService());
Hibernate.initialize(requestMessage.getService().getGroupService());
Hibernate.initialize(requestMessage.getRequestMessageProfessionals());
for (RequestMessageProfessional rmp : requestMessage.getRequestMessageProfessionals()) {
Hibernate.initialize(rmp.getProfessional());
rmp.setRequestMessage(null); // **
}
}
为了使代码可读,将大注释从注释// **
移到下面。
java.lang.StackOverflowError [请求处理失败;嵌套的异常是org.springframework.http.converter.HttpMessageNotWritableException:无法编写JSON:无限递归(StackOverflowError)(通过参考链:com.service.pegazo.bo.RequestMessageProfessional [“ requestMessage”]-> com.service.pegazo。 bo.RequestMessage [“ requestMessageProfessionals”]