【问题标题】:eclipselink/Moxy : inheritance and attribute name oveloading based on typeeclipselink/Moxy:基于类型的继承和属性名重载
【发布时间】:2012-02-15 02:37:07
【问题描述】:

我正面临一个编组/解组问题,涉及使用 MOXy 的 JAXB 实现和外部元数据绑定文件的继承和多态性。

我无法控制 XML 文件或模型类。

模型中有多个类继承其他 DTO 类。 这是我正在使用的环境的示例。此示例仅用于某些语法目的,真实环境涉及嵌套继承,集合等:

这里是要继承的类

  class A {

        private String name;

        public String getName(){
              return name;
        }

        public void setName(String value){
              name = value;
        }

  } 

这是一个继承的类

  class B extends A {

        private String attrFromB;

        public String getAttrFromB(){
              return attrFromB;
        }

        public void setAttrFromB(String value){
              attrFromB = value;
        }
  } 

还有一个

  class C extends A {

        private String attrFromC;

        public String getAttrFromC(){
              return attrFromC;
        }

        public void setAttrFromC(String value){
              attrFromC= value;
        }
  } 

这是一个容器类

  class MyContainerClass{

        private A myObject;

        public A getMyObject(){
           return myObject;
        }

        public void setMyObject(A value){
           myObject = value;
        }
  }

这是在 MyContainer 包含 A 的情况下它应该生成的 XML

  <MyContainer>
        <MyObject nameA="foo" />
  </MyContainer>

包含 B 的 MyContainer

  <MyContainer>
        <MyObject nameB="foo" attrFromB="bar" />
  </MyContainer>

和包含C的MyContainer

  <MyContainer>
        <MyObject nameC="foo" attrFromC="bar" />
  </MyContainer>

所以你已经可以看到即将出现的问题...

这是我要编写的映射文件:

  <?xml version="1.0"?>
     <xml-bindings 
        xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        package-name="com.test.example"
        version="2.1">  

        <java-type name="A" xml-accessor-type="NONE">
           <xml-root-element name="MyObject" />
           <java-attributes>
              <xml-element java-attribute="name" xml-path="@nameA" />
           </java-attributes>
        </java-type>  

        <java-type name="B" xml-accessor-type="NONE">
           <xml-root-element name="MyObject" />
           <xml-see-also>
              com.test.example.A
           </xml.see.also>
           <java-attributes>
              <xml-element java-attribute="name" xml-path="@nameB" />
              <xml-element java-attribute="attrFromB" xml-path="@attrFromB" />
           </java-attributes>
        </java-type>

        <java-type name="C" xml-accessor-type="NONE">
           <xml-root-element name="MyObject" />
           <xml-see-also>
              com.test.example.A
           </xml.see.also>
           <java-attributes>
              <xml-element java-attribute="name" xml-path="@nameC" />
              <xml-element java-attribute="attrFromC" xml-path="@attrFromC" />
           </java-attributes>
        </java-type>

        <java-type name="MyContainer" xml-accessor-type="NONE">
           <xml-root-element name="MyContainer" />
           <java-attributes>
              <xml-element java-attribute="myObject" type="com.test.example.A" xml-path="MyObject" />
           </java-attributes>
        </java-type>

     </xml-bindings>

第一个问题是,如果我像这样绑定类,我会得到以下异常:

  [Exception [EclipseLink-44] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DescriptorException
  Exception Description: Missing class indicator field from database row [UnmarshalRecord()].

第一个问题:我知道这是正常的,Jaxb 需要一些方法来确定 MyContaioner.myObject 属性的类型。问题是我无法访问传入的 XML 文件,因此我无法向它们添加 xsi:type 字段。有没有办法根据其中是否存在特定属性来确定一个类?不管它的价值。如果源 xml 包含 @attrFromC 属性,我知道对象应该是 C 类型。如果它包含 attrFromB,它就是 B。


第二个问题是“name”属性在B和C内部不存在,所以jaxb忽略了em。

  --Ignoring attribute [name] on class [com.test.example.B] as no Property was generated for it.
  --Ignoring attribute [name] on class [com.test.example.C] as no Property was generated for it.

第二个问题:另一个问题是我不知道 Jaxb 是否能够像 XML 文件中预期的那样覆盖 xml 属性名称(@nameA、@nameB 和 nameC 都指 A .name),有什么办法吗?

提前感谢您的宝贵时间。

【问题讨论】:

    标签: inheritance jaxb polymorphism eclipselink moxy


    【解决方案1】:

    以下是您的问题的答案。问题 2 的答案,也是问题 1 的答案。


    第一个问题:我知道这是正常的,Jaxb 需要一些方法 确定 MyContainer.myObject 属性的类型。问题 是我无法访问传入的 XML 文件,所以我无法添加 xsi:type 字段给他们。有没有办法根据 是否存在特定属性?不管它的价值。 如果源 xml 包含 @attrFromC 属性,我知道该对象 应该是C类型。如果它包含attrFromB,它就是B。

    对于此用例,您可以利用 EclipseLink JAXB (MOXy) 中的 ClassExtractor 扩展:

    MyClassExtractor

    ClassExtractor 是一些代码,您可以实现这些代码来帮助 MOXy 确定它应该实例化哪个类。您会收到一个Record,您可以通过 XPath 请求当前元素的属性是否存在,以确定应该实例化哪个类。

    package com.test.example;
    
    import org.eclipse.persistence.descriptors.ClassExtractor;
    import org.eclipse.persistence.sessions.*;
    
    public class MyClassExtractor extends ClassExtractor{
    
        @Override
        public Class<?> extractClassFromRow(Record record, Session session) {
            if(null != record.get("@attrFromB")) {
                return B.class;
            } else if(null != record.get("@attrFromC")) {
                return C.class;
            } else {
                return A.class;
            }
        }
    
    }
    

    元数据 (oxm.xml)

    您可以使用@XmlClassExtractor 注解配置ClassExtractor。您也可以通过外部元数据文件执行此操作。我已经修改了您问题中包含的内容:

    <?xml version="1.0"?>
    <xml-bindings 
        xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        package-name="com.test.example"
        version="2.3">
        <java-types>
            <java-type name="A" xml-accessor-type="NONE">
               <xml-class-extractor class="com.test.example.MyClassExtractor"/>
               <xml-root-element name="MyObject" />
               <java-attributes>
                  <xml-attribute java-attribute="name" name="nameA" />
               </java-attributes>
            </java-type>  
            <java-type name="B" xml-accessor-type="NONE">
               <xml-root-element name="MyObject" />
               <java-attributes>
                  <xml-attribute java-attribute="name" name="nameB" />
                  <xml-attribute java-attribute="attrFromB"/>
               </java-attributes>
            </java-type>
            <java-type name="C" xml-accessor-type="NONE">
               <xml-root-element name="MyObject" />
               <java-attributes>
                  <xml-attribute java-attribute="name" name="nameC" />
                  <xml-attribute java-attribute="attrFromC"/>
               </java-attributes>
            </java-type>
            <java-type name="MyContainerClass" xml-accessor-type="NONE">
               <xml-root-element name="MyContainer" />
               <java-attributes>
                  <xml-element java-attribute="myObject" name="MyObject" />
               </java-attributes>
            </java-type>
        </java-types>
    </xml-bindings>
    

    演示

    以下演示代码从您的问题中解组每个 XML 文档,并输出 myObject 属性所持有的类型:

    package com.test.example;
    
    import java.io.StringReader;
    import java.util.*;
    import javax.xml.bind.*;
    import org.eclipse.persistence.jaxb.JAXBContextFactory;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            Map<String, Object> properties = new HashMap<String, Object>();
            properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "com/test/example/oxm.xml");
            JAXBContext jc = JAXBContext.newInstance(new Class[] {MyContainerClass.class}, properties);
            Unmarshaller unmarshaller = jc.createUnmarshaller();
    
            StringReader aXml = new StringReader("<MyContainer><MyObject nameA='foo'/></MyContainer>");
            MyContainerClass myContainerA = (MyContainerClass) unmarshaller.unmarshal(aXml);
            System.out.println(myContainerA.getMyObject().getClass());
    
            StringReader bXml = new StringReader("<MyContainer><MyObject nameB='foo' attrFromB='bar'/></MyContainer>");
            MyContainerClass myContainerB = (MyContainerClass) unmarshaller.unmarshal(bXml);
            System.out.println(myContainerB.getMyObject().getClass());
    
            StringReader cXml = new StringReader("<MyContainer><MyObject nameC='foo' attrFromC='bar'/></MyContainer>");
            MyContainerClass myContainerC = (MyContainerClass) unmarshaller.unmarshal(cXml);
            System.out.println(myContainerC.getMyObject().getClass());
        }
    
    }
    

    输出

    [EL Warning]: 2012-01-20 10:36:41.828--Ignoring attribute [name] on class [com.test.example.B] as no Property was generated for it.
    [EL Warning]: 2012-01-20 10:36:41.828--Ignoring attribute [name] on class [com.test.example.C] as no Property was generated for it.
    class com.test.example.A
    class com.test.example.B
    class com.test.example.C
    

    第二个问题:另一个问题是我不知道 Jaxb 是否是 能够像内部预期的那样覆盖 xml 属性名称 XML 文件(@nameA、@nameB 和 nameC 都引用 A.name)是 有办法吗?

    您可以利用XmlAdapter 来回答这个问题。这种方法也可以用来回答你的第一个问题:

    适配器

    package com.test.example;
    
    import javax.xml.bind.annotation.XmlAttribute;
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    
    public class AAdapter extends XmlAdapter<AAdapter.AdaptedA, A> {
    
        @Override
        public AdaptedA marshal(A a) throws Exception {
            if(null == a) {
                return null;
            }
            AdaptedA adaptedA = new AdaptedA();
            if(a instanceof C) {
                C c = (C) a;
                adaptedA.nameC = c.getName();
                adaptedA.attrFromC = c.getAttrFromC();
            } else if(a instanceof B) {
                B b = (B) a;
                adaptedA.nameB = b.getName();
                adaptedA.attrFromB = b.getAttrFromB();
            } else if(a instanceof A) {
                adaptedA.nameA = a.getName();
            }
            return adaptedA;
        }
    
        @Override
        public A unmarshal(AdaptedA adaptedA) throws Exception {
            if(null == adaptedA) {
                return null;
            }
            if(null != adaptedA.attrFromC) {
                C c = new C();
                c.setName(adaptedA.nameC);
                c.setAttrFromC(adaptedA.attrFromC);
                return c;
            } else if(null != adaptedA.attrFromB) {
                B b = new B();
                b.setName(adaptedA.nameB);
                b.setAttrFromB(adaptedA.attrFromB);
                return b;
            } 
            A a = new A();
            a.setName(adaptedA.nameA);
            return a;
        }
    
        public static class AdaptedA {
            @XmlAttribute public String nameA;
            @XmlAttribute public String nameB;
            @XmlAttribute public String nameC;
            @XmlAttribute public String attrFromB;
            @XmlAttribute public String attrFromC;
        }
    
    }
    

    元数据 (oxm-2.xml)

    <?xml version="1.0"?>
    <xml-bindings 
        xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        package-name="com.test.example"
        version="2.3">
        <java-types>
            <java-type name="MyContainerClass" xml-accessor-type="NONE">
               <xml-root-element name="MyContainer" />
               <java-attributes>
                  <xml-element java-attribute="myObject" name="MyObject">
                    <xml-java-type-adapter value="com.test.example.AAdapter"/>
                  </xml-element>
               </java-attributes>
            </java-type>
        </java-types>
    </xml-bindings>
    

    演示2

    package com.test.example;
    
    import java.io.StringReader;
    import java.util.*;
    import javax.xml.bind.*;
    import org.eclipse.persistence.jaxb.JAXBContextFactory;
    
    public class Demo2 {
    
        public static void main(String[] args) throws Exception {
            Map<String, Object> properties = new HashMap<String, Object>();
            properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "com/test/example/oxm-2.xml");
            JAXBContext jc = JAXBContext.newInstance(new Class[] {MyContainerClass.class}, properties);
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    
            StringReader aXml = new StringReader("<MyContainer><MyObject nameA='foo'/></MyContainer>");
            MyContainerClass myContainerA = (MyContainerClass) unmarshaller.unmarshal(aXml);
            System.out.println(myContainerA.getMyObject().getClass());
            marshaller.marshal(myContainerA, System.out);
    
            StringReader bXml = new StringReader("<MyContainer><MyObject nameB='foo' attrFromB='bar'/></MyContainer>");
            MyContainerClass myContainerB = (MyContainerClass) unmarshaller.unmarshal(bXml);
            System.out.println(myContainerB.getMyObject().getClass());
            marshaller.marshal(myContainerB, System.out);
    
            StringReader cXml = new StringReader("<MyContainer><MyObject nameC='foo' attrFromC='bar'/></MyContainer>");
            MyContainerClass myContainerC = (MyContainerClass) unmarshaller.unmarshal(cXml);
            System.out.println(myContainerC.getMyObject().getClass());
            marshaller.marshal(myContainerC, System.out);
        }
    
    }
    

    输出

    class com.test.example.A
    <?xml version="1.0" encoding="UTF-8"?>
    <MyContainer>
       <MyObject nameA="foo"/>
    </MyContainer>
    class com.test.example.B
    <?xml version="1.0" encoding="UTF-8"?>
    <MyContainer>
       <MyObject nameB="foo" attrFromB="bar"/>
    </MyContainer>
    class com.test.example.C
    <?xml version="1.0" encoding="UTF-8"?>
    <MyContainer>
       <MyObject nameC="foo" attrFromC="bar"/>
    </MyContainer>
    

    【讨论】:

    • 阅读您答案的第一部分,这已经非常有帮助了。谢谢。等不及第二部分了。
    • 只是为了确定,如果在 ClassExtractor 中,我想测试是否存在节点而不是属性。我可以使用 record.get("SomeNodeName") 或 record.get("SomeNodeName/text()") 吗?
    • @user1121108 - 我已经为第二部分添加了答案。在类提取器中,您将只能测试属性的存在。在执行的那个时候,子元素还没有被处理。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    • 2021-05-07
    • 1970-01-01
    相关资源
    最近更新 更多