eclipselink / Moxy:基于类型的继承和属性名称开发 [英] eclipselink/Moxy : inheritance and attribute name oveloading based on type
问题描述
我正在使用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屋!