JAXB EclipseLink Moxy:在对象图中检测到循环

问题描述 投票:2回答:1

我在编组具有双向关系的对象图时遇到奇怪的行为。

错误消息是:

异常[EclipseLink-25037](Eclipse持久性服务-2.5.2.v20140319-9ad6abd):org.eclipse.persistence.exceptions.XMLMarshalException

异常描述:在对象图中检测到循环。这个将导致无限循环:com.moxytest.Cycle$Doc@27f723->com.moxytest.Cycle$Pub@670b40af-> com.moxytest.Cycle$Agree@4923ab24-> com.moxytest.Cycle$Agen@44c8afef-> com.moxytest.Cycle$Acc@7b69c6ba->com.moxytest.Cycle$Med@46daef40->com.moxytest.Cycle$Pag@12f41634-> com.moxytest.Cycle$Doc@27f723

[异常消息中的对象图似乎不是按处理顺序排列的。调试XPathObjectBuilder显示,在cycleDetectionStack上,对象按以下顺序推送:

com.moxytest.Cycle$Doc
com.moxytest.Cycle$Pag
com.moxytest.Cycle$Med
com.moxytest.Cycle$Acc
com.moxytest.Cycle$Agen
com.moxytest.Cycle$Agree
com.moxytest.Cycle$Pub 

我不明白为什么会引发异常,因为我认为使用@XmlInverseReference会很好。仅在对象图更加复杂时才会出现此问题。这是代码:

import java.io.StringReader;
import java.io.StringWriter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.oxm.MediaType;
import org.eclipse.persistence.oxm.annotations.XmlInverseReference;

public final class Cycle {

    private static JAXBContext JAXB_CONTEXT;

    static {
        try {
            JAXB_CONTEXT = (JAXBContext) JAXBContext.newInstance(Doc.class);
        } catch (JAXBException ex) {
            ex.printStackTrace();
        }
    }

    @XmlTransient
    @XmlAccessorType(XmlAccessType.PROPERTY)
    public static abstract class Entity {

        private String id;

        @XmlElement
        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

    }

    @XmlRootElement
    public static class Doc extends Entity {

        private Pag pag;

        private Pub pub;

        @XmlElement
        @XmlInverseReference(mappedBy = "doc")
        public Pag getPag() {
            return pag;
        }

        public void setPag(Pag pag) {
            this.pag = pag;
        }

        @XmlElement
        @XmlInverseReference(mappedBy = "docs")
        public Pub getPub() {
            return pub;
        }

        public void setPub(Pub pub) {
            this.pub = pub;
        }

    }

    @XmlRootElement
    public static class Acc extends Entity {

        private Agen agen;

        @XmlElement
        public Agen getAgen() {
            return agen;
        }

        public void setAgen(Agen agen) {
            this.agen = agen;
        }

    }

    @XmlRootElement
    public static class Med extends Entity {

        private Pag pag;

        private Acc acc;

        @XmlElement
        @XmlInverseReference(mappedBy = "meds")
        public Pag getPag() {
            return pag;
        }

        public void setPag(Pag pag) {
            this.pag = pag;
        }

        @XmlElement
        public Acc getAcc() {
            return acc;
        }

        public void setAcc(Acc acc) {
            this.acc = acc;
        }

    }

    @XmlRootElement
    public static class Pag extends Entity {

        private Doc doc;

        private List<Med> meds = new ArrayList<>();

        public void setDoc(Doc doc) {
            this.doc = doc;
        }

        @XmlElement
        @XmlInverseReference(mappedBy = "pag")
        public Doc getDoc() {
            return doc;
        }

        @XmlElement
        @XmlInverseReference(mappedBy = "pag")
        public List<Med> getMeds() {
            return meds;
        }

        public void setMeds(List<Med> meds) {
            this.meds = meds;
        }

    }

    @XmlRootElement
    public static class Pub extends Entity {

        private List<Doc> docs;

        private Agree agree;

        public List<Doc> getDocs() {
            return docs;
        }

        @XmlElement
        @XmlInverseReference(mappedBy = "pub")
        public void setDocs(List<Doc> docs) {
            this.docs = docs;
        }

        @XmlElement
        @XmlInverseReference(mappedBy = "pub")
        public Agree getAgree() {
            return agree;
        }

        public void setAgree(Agree agree) {
            this.agree = agree;
        }

    }

    @XmlRootElement
    public static class Agree extends Entity {

        private Pub pub;

        private Agen agen;

        @XmlElement
        @XmlInverseReference(mappedBy = "agree")
        public Pub getPub() {
            return pub;
        }

        public void setPub(Pub pub) {
            this.pub = pub;
        }

        @XmlElement
        @XmlInverseReference(mappedBy = "agrees")
        public Agen getAgen() {
            return agen;
        }

        public void setAgen(Agen agen) {
            this.agen = agen;
        }

    }

    @XmlRootElement
    public static class Agen extends Entity {

        private List<Agree> agrees;

        @XmlElement
        @XmlInverseReference(mappedBy = "agen")
        public List<Agree> getAgrees() {
            return agrees;
        }

        public void setAgrees(List<Agree> agrees) {
            this.agrees = agrees;
        }

    }

    public static void main(String[] args) throws JAXBException {
        Pag pag = new Pag();

        Med med = new Med();
        med.setPag(pag);
        pag.getMeds().add(med);

        Doc doc = new Doc();
        pag.setDoc(doc);
        doc.setPag(pag);

        Pub pub = new Pub();
        pub.setDocs(Arrays.asList(doc));
        doc.setPub(pub);

        Agree agree = new Agree();
        agree.setPub(pub);
        pub.setAgree(agree);

        Agen agen = new Agen();
        agen.setAgrees(Arrays.asList(agree));
        agree.setAgen(agen);

        Acc acc = new Acc();
        acc.setAgen(agen);
        med.setAcc(acc);

        String marshal = marshal(doc);
        System.err.println(marshal);
        Doc ud = unmarshal(Doc.class, marshal);
        String marshal2 = marshal(ud);
        System.err.println("\n\n" + marshal2);
        System.err.println("Equals? " + marshal.equals(marshal2));
    }

    public static String marshal(Object toMarshal) throws JAXBException {
        Marshaller marshaller = JAXB_CONTEXT.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//        marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_XML);
        marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
        StringWriter sw = new StringWriter();
        marshaller.marshal(toMarshal, sw);
        return sw.toString();
    }

    @SuppressWarnings("unchecked")
    public static <T> T unmarshal(Class<T> unmarshallingClass, String str) throws JAXBException {
        Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
//        unmarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_XML);
        unmarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
        return (T) unmarshaller.unmarshal(new StringReader(str));
    }

}

如果我在类Pub中的setDocs()处删除@XMLElement,它将起作用。但是然后,在编组Pub时,我失去了docs对象。

您能帮忙吗?

===============更新

EclipseLink的XMLInverseReference的实现未考虑传递周期。在编组期间,它将创建一个cycleDetectionStack,在其中放置所有编组的对象。如果遇到XMLInverseReference,它将尝试通过从cycleDetectionStack-2获取对象来查找所有者。这适用于对象之间的直接循环,但不适用于A-> B-> C-> A的传递循环。

通过查看源代码,我发现了一种使之在传递周期中起作用的简单方法。我要做的就是遍历整个cycleDetectionStack:

XMLCompositeCollectionMappingNodeValue:

if ((isInverseReference || xmlCompositeCollectionMapping.getInverseReferenceMapping() != null) && size >= 2) {
    //Object owner = marshalRecord.getCycleDetectionStack().get(size - 2);
    // Bugfix InverseRef has no effect on "transitive" references within object graph
    CycleDetectionStack cycleDetectionStack = marshalRecord.getCycleDetectionStack();
    for (Object stackedObj : cycleDetectionStack) {
        try {
            if (cp.contains(stackedObj, collection, session)) {
                return false;
            }
        } catch (ClassCastException e) {
            // For Bug #416875
        }
    }
}

第二类是具有完全相同解决方案的XMLCompositeObjectMappingNodeValue:

if ((isInverseReference || xmlCompositeObjectMapping.getInverseReferenceMapping() != null) && objectValue != null && size >= 2) {
    //Object owner = marshalRecord.getCycleDetectionStack().get(size - 2);
    // Bugfix InverseRef has no effect on "transitive" references within object graph
    CycleDetectionStack cycleDetectionStack = marshalRecord.getCycleDetectionStack();
    for (Object stackedObj : cycleDetectionStack) {
        if (objectValue.equals(stackedObj)) {
            return false;
        }
    }
}

也许开发者有充分的理由只看所有者。至于我,我找不到为什么要忍受这种缺点。我们有一个大型域模型,其中所有实体都通过json转码。此后我们没有任何错误。

jaxb eclipselink moxy jaxb2
1个回答
0
投票

这称为JSOG,在JSON结构中您具有循环,并且EclipseLinks的默认Moxy Json Provider无法序列化此类POJO。

您需要Jackson Json Provider,而不是修改模型Pojo类。

我假设您正在使用JaxRs REST api尝试序列化Pojos。在那种情况下,您可以很容易地使用maven的Jackson软件包。只需为jaxb提供程序和对象映射器创建提供程序。可以在这里找到更多详细信息:

https://stackoverflow.com/a/60319306/5076414

© www.soinside.com 2019 - 2024. All rights reserved.