eclipselink / Moxy:基于类型的继承和属性名称开发 [英] eclipselink/Moxy : inheritance and attribute name oveloading based on type

查看:144
本文介绍了eclipselink / Moxy:基于类型的继承和属性名称开发的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用MOXy的JAXB实现和外部元数据绑定文件来处理涉及继承和多态的编组/解组问题。

I'm facing a marshalling/unmarshalling problem involving inheritance and polymorphism using MOXy's JAXB implementation and external metadata bindings file.

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

I have no control on the XML files or the model classes.

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

There are multiple classes inside the model that inherit other DTO classes. Here is an example of the environment I'm working in. This example is only here for some syntax purpose, the real environment involves nested inheritance, collections etc. :

以下是将继承的类

  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;
        }
  }

这是它应该在案例中生成的XML MyContainer包含A

Here is the XML that it should produce in the case of MyContainer containing A

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

包含B的MyContainer

MyContainer containing B

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

包含C的MyContainer

And MyContainer containing C

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

所以你已经可以看到问题......

So you can already see problems in the horizon...

这是我要写的映射文件:

Here is the mapping file that I would write :

  <?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>






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


The first problem is that if I bind the classes like that, I get the following exception :

  [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。

1st question : I understand that this is normal, Jaxb needs some way to determine the type of MyContaioner.myObject attribute. The problem is that I have no access to the incoming XML files, so I cant add xsi:type fields to them. Is there a way to determine a class based on the presence of a specific attribute in it ? regardless of it's value. If the source xml contains a @attrFromC attribute, I know the object should be of type C. If it contains attrFromB, it's B.

第二个问题是B和C中不存在name属性,因此jaxb忽略了em。

The second problem is that the "name" attribute doesn't exist inside B and C, so jaxb ignores 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),有没有办法做到这一点?

2nd question : The other problem is that I dont know if Jaxb is capable of overriding xml attribute names like it is expected inside the XML file (@nameA, @nameB and nameC all referring to A.name), is there a way to do it ?

提前感谢您的时间。

推荐答案

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

Below are the answers to your questions. The answer to question 2, is also an answer to question 1.


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

1st question : I understand that this is normal, Jaxb needs some way to determine the type of MyContaioner.myObject attribute. The problem is that I have no access to the incoming XML files, so I cant add xsi:type fields to them. Is there a way to determine a class based on the presence of a specific attribute in it ? regardless of it's value. If the source xml contains a @attrFromC attribute, I know the object should be of type C. If it contains attrFromB, it's B.

您可以在 ClassExtractor 扩展名此用例的rel =nofollow> EclipseLink JAXB(MOXy)

You can leverage the ClassExtractor extension in EclipseLink JAXB (MOXy) for this use case:

MyClassExtractor

一个 ClassExtractor 是一些代码,您可以实现这些代码来帮助MOXy确定它应该实现哪个类。您将传递一个记录,您可以通过XPath询问当前元素是否存在属性,以确定应该实例化哪个类。

A ClassExtractor is some code that you can implement to help MOXy determine which class it should instanitate. You are passed a Record and you can ask for the presence of the attributes at the current element by XPath to determine which class should be instantiated.

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)

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

You can configure the ClassExtractor using the @XmlClassExtractor annotation. You can also do this via the external metadata file. I have adapted the one included in your question to include this:

<?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 属性所持有的类型:

The following demo code unmarshals each of the XML documents from your question, and outputs the type being held by the myObject property:

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),是
还有办法吗?

2nd question : The other problem is that I dont know if Jaxb is capable of overriding xml attribute names like it is expected inside the XML file (@nameA, @nameB and nameC all referring to A.name), is there a way to do it ?

您可以利用 XmlAdapter 来解决这个问题。此方法也可用于回答您的第一个问题:

You can leverage an XmlAdapter for this question. This approach can also be used to answer your first question:

AAdapter

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>

这篇关于eclipselink / Moxy:基于类型的继承和属性名称开发的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆