【问题标题】:Resolving cyclic references in jaxb解决 jaxb 中的循环引用
【发布时间】:2013-08-30 10:00:04
【问题描述】:

在实现我的项目的 Web 服务层时,我正在处理一些循环引用。我正在使用 jaxb(最新版本,2.2.7),甚至我也看过一些提示,如 herehere 我无法让它工作。这是关于我的问题的基本SSCCE

/*
* The service interface
*/
@WebService
public interface IMyWS {

    @WebMethod
    public List<Class1> cyclicTest();

}

/*
* Interface implementation
*/
@WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
public class MyWS implements IMyWS {

    @XmlRootElement
    public static class Class1 {

        @XmlTransient
        private Class2 class2;

        public Class1() {

        }

        public Class1(Class2 refClass) {
            class2 = refClass;
        }

        public Class2 getClass2() {
            return class2;
        }

        public void setClass2(Class2 class2) {
            this.class2 = class2;
        }

        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }

    }

    @XmlRootElement
    public static class Class2 {

        private Class1 class1;

        public Class2() {

        }

        public Class1 getClass1() {
            return class1;
        }

        public void setClass1(Class1 class1) {
            this.class1 = class1;
        }

        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }
    }

    @Override
    public List<Class1> cyclicTest() {
        //I create an instance of each class, having them a cyclic reference to the other instance
        Class2 class2 = new Class2();
        Class1 class1 = new Class1(class2);
        class2.setClass1(class1);
        return Arrays.asList(class1);
    }

}

我在调用cyclicTest() 时实际处理的异常:

Caused by: javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.SAXException2: Se ha detectado un ciclo en el gráfico de objeto. Esto provocará un XML con profundidad infinita: Class1 -> Class2 -> Class1]
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:326)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:178)
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:537)
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:233)
    ... 50 more
Caused by: com.sun.istack.SAXException2: Se ha detectado un ciclo en el gráfico de objeto. Esto provocará un XML con profundidad infinita: Class1 -> Class2 -> Class1
    at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:249)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.pushObject(XMLSerializer.java:537)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:631)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:158)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:158)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
    at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:69)
    at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:172)
    at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:159)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:156)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:131)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:333)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:340)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:76)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
    ... 53 more

我认为我设置了正确的注释。我到底错过了什么?

【问题讨论】:

    标签: java xml jaxb cyclic-reference


    【解决方案1】:

    使用@XmlID 和@XmlIDREF 应该可以解决您的问题。

    这就是我所做的:

    我在 Class 1 中添加了一个 String 字段用作 id,并用 @XmlID 对其进行了注释。 然后,我用@XmlIDREF 注释了setClass1() 方法。我的测试在下面的main方法中。

    /*
     * Interface implementation
     */
    @WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
    public class MyWS implements IMyWS {
    
        @XmlRootElement
        public static class Class1 {
    
            @XmlID
            private String id;
    
            private Class2 class2;
    
            public Class1() {
                this.id = UUID.randomUUID().toString();
            }
    
            public Class1(Class2 refClass) {
                this();
                class2 = refClass;
            }
    
            public Class2 getClass2() {
                return class2;
            }
    
            public void setClass2(Class2 class2) {
                this.class2 = class2;
            }
    
            @Override
            public String toString() {
                return this.getClass().getSimpleName();
            }
    
        }
    
        @XmlRootElement
        public static class Class2 {
    
            private Class1 class1;
    
            public Class2() {
    
            }
    
            public Class1 getClass1() {
                return class1;
            }
    
            @XmlIDREF
            public void setClass1(Class1 class1) {
                this.class1 = class1;
            }
    
            @Override
            public String toString() {
                return this.getClass().getSimpleName();
            }
        }
    
        @Override
        public List<Class1> cyclicTest() {
            //I create an instance of each class, having them a cyclic reference to the other instance
            Class2 class2 = new Class2();
            Class1 class1 = new Class1(class2);
            class2.setClass1(class1);
            return Arrays.asList(class1);
        }
    
        public static void main(String[] args) throws JAXBException {
    
            JAXBContext ctx = JAXBContext.newInstance(Class1.class);
            Marshaller m = ctx.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            List<Class1> class1s = new MyWS().cyclicTest();
    
            for (Class1 c1 : class1s){
                m.marshal(c1, System.out);
            }
    
        }
    
    }
    

    输出:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <class1>
        <id>e9e53e73-9a96-4c7c-b919-3ed3d7aa4c5e</id>
        <class2>
            <class1>e9e53e73-9a96-4c7c-b919-3ed3d7aa4c5e</class1>
        </class2>
    </class1>
    

    【讨论】:

    • 感谢您的回复,我明天试试!
    【解决方案2】:

    在私有属性上添加@XmlTransient 时,应将XmlAccessType(默认为XmlAccessType.PUBLIC_MEMBER)更改为XmlAccessType.PROPERTY

    PUBLIC_MEMBER 是 JAXB 中的默认访问类型。这意味着 JAXB 实现将为以下内容生成绑定:公共字段、注释字段、属性

    从 W A 复制的代码:

    @WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
    public class MyWS implements IMyWS {
    
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.PROPERTY)
    public static class Class1 {
    
        private Class2 class2;
    
        public Class1() {
    
        }
    
        public Class1(Class2 refClass) {
            class2 = refClass;
        }
    
        @XmlTransient
        public Class2 getClass2() {
            return class2;
        }
    
        public void setClass2(Class2 class2) {
            this.class2 = class2;
        }
    
        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }
    
    }
    
    @XmlRootElement
    public static class Class2 {
    
        private Class1 class1;
    
        public Class2() {
    
        }
    
        public Class1 getClass1() {
            return class1;
        }
    
        public void setClass1(Class1 class1) {
            this.class1 = class1;
        }
    
        @Override
        public String toString() {
            return this.getClass().getSimpleName();
        }
    }
    
    public List<Class1> cyclicTest() {
        //I create an instance of each class, having them a cyclic reference to the other instance
        Class2 class2 = new Class2();
        Class1 class1 = new Class1(class2);
        class2.setClass1(class1);
        return Arrays.asList(class1);
    }
    
    
    public static void main(String[] args) throws JAXBException {
    
        JAXBContext ctx = JAXBContext.newInstance(Class1.class);
        Marshaller m = ctx.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        List<Class1> class1s = new MyWs().cyclicTest();
    
        for (Class1 c1 : class1s){
            m.marshal(c1, System.out);
        }
    
    }
    

    【讨论】:

    • 尝试setting @XmlAccessorType(javax.xml.bind.annotation.XmlAccessType.PROPERTY) 到我的根元素,不起作用。也使两个字段public,也不是。我一直遇到同样的异常。
    • 我认为你错过了一些东西:我用一些源代码编辑了帖子:@XmlAccessorType 正在按预期工作:你没有任何循环问题。
    • 您的回答对于我的示例问题是正确的。但是,在我的原始项目中,我最好使用对象的引用 id 以便省略它。这就是@W A 的答案,所以我勾选您的回复并给他赏金。非常感谢!
    【解决方案3】:

    添加 W Almir 的答案,为了更简单地为对象生成唯一 ID(无需向构造函数添加代码),请注释一个返回 id 的方法。例如:

    @XmlID
    public String getId()
    {
        return Integer.toString(System.identityHashCode(this));
    }
    

    这似乎不仅适用于将 Java 对象的循环图转换为 XML,也适用于将 XML 转换为 Java 对象的循环图。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-19
      • 2012-05-09
      • 2011-02-13
      • 1970-01-01
      • 2012-03-21
      相关资源
      最近更新 更多