JAXB 继承,解组到编组类的子类
Posted
技术标签:
【中文标题】JAXB 继承,解组到编组类的子类【英文标题】:JAXB inheritance, unmarshal to subclass of marshaled class 【发布时间】:2010-10-11 19:26:29 【问题描述】:我正在使用 JAXB 来读取和写入 XML。我想要的是使用一个基本的 JAXB 类进行编组和一个继承的 JAXB 类进行解组。这是为了允许发送方 Java 应用程序将 XML 发送到另一个接收方 Java 应用程序。发送方和接收方将共享一个公共 JAXB 库。我希望接收方将 XML 解组为接收方特定的 JAXB 类,该类扩展了通用 JAXB 类。
例子:
这是发送方使用的通用 JAXB 类。
@XmlRootElement(name="person")
public class Person
public String name;
public int age;
这是在解组 XML 时使用的接收器特定的 JAXB 类。接收器类具有特定于接收器应用程序的逻辑。
@XmlRootElement(name="person")
public class ReceiverPerson extends Person
public doReceiverSpecificStuff() ...
编组按预期工作。问题在于解组,尽管 JAXBContext 使用子类 ReceiverPerson
的包名称,但它仍然解组到 Person
。
JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson);
我想要的是解组到ReceiverPerson
。我能够做到这一点的唯一方法是从Person
中删除@XmlRootElement
。不幸的是,这样做会阻止 Person
被编组。就好像 JAXB 从基类开始并向下工作,直到找到具有适当名称的第一个 @XmlRootElement
。我尝试添加一个createPerson()
方法,该方法将ReceiverPerson
返回到ObjectFactory
,但这无济于事。
【问题讨论】:
【参考方案1】:下面的sn-p是一个绿灯的Junit 4测试方法:
@Test
public void testUnmarshallFromParentToChild() throws JAXBException
Person person = new Person();
int age = 30;
String name = "Foo";
person.name = name;
person.age= age;
// Marshalling
JAXBContext context = JAXBContext.newInstance(person.getClass());
Marshaller marshaller = context.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(person, writer);
String outString = writer.toString();
assertTrue(outString.contains("</person"));
// Unmarshalling
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader reader = new StringReader(outString);
RecieverPerson reciever = (RecieverPerson)unmarshaller.unmarshal(reader);
assertEquals(name, reciever.name);
assertEquals(age, reciever.age);
重要的部分是使用JAXBContext.newInstance(Class... classesToBeBound)
方法进行解组上下文:
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
通过此调用,JAXB 将计算指定类的引用闭包并识别RecieverPerson
。测试通过。如果您更改参数顺序,您将得到一个java.lang.ClassCastException
(因此它们必须按此顺序传递)。
【讨论】:
这个实际上效果最好,如果您可以为类使用 JAXB 注释,则不需要适配器。【参考方案2】:您使用的是 JAXB 2.0 对吗? (JDK6 起)
有一个类:
javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>
哪一个可以子类化,并覆盖以下方法:
public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;
例子:
public class YourNiceAdapter
extends XmlAdapter<ReceiverPerson,Person>
@Override public Person unmarshal(ReceiverPerson v)
return v;
@Override public ReceiverPerson marshal(Person v)
return new ReceiverPerson(v); // you must provide such c-tor
使用方法如下:
@Your_favorite_JAXB_Annotations_Go_Here
class SomeClass
@XmlJavaTypeAdapter(YourNiceAdapter.class)
Person hello; // field to unmarshal
我很确定,通过使用这个概念,您可以自己控制编组/解组过程(包括选择要构造的正确 [sub|super] 类型)。
【讨论】:
我试过了,但它不起作用。无论我尝试什么,ObjectFactory 或我的 XmlAdapter 都不会被调用。从我所读到的 Sun 的 JAXB 通过静态类引用解析。 Glassfish 的实现似乎更有希望jaxb.dev.java.net/guide/Adding_behaviors.html XmlJavaAdapters 适合使非 JAXB 注释的类可用于 JAXB。因为 Person 和 ReceiverPerson(和/或他们的 superlass,如果有的话)有 JAXB 注释,所以它不起作用。您需要没有 JAXB 的 Person 和 ReceiverPerson,以及两者的适配器。 您是否尝试过为 marshall|unmarshall 交换类型? [...] 扩展 XmlAdapter子类 Person 两次,一次用于接收者,一次用于发送者,并且只将 XmlRootElement 放在这些子类上(留下超类 Person
,没有 XmlRootElement)。请注意,发送者和接收者都共享相同的 JAXB 基类。
@XmlRootElement(name="person")
public class ReceiverPerson extends Person
// receiver specific code
@XmlRootElement(name="person")
public class SenderPerson extends Person
// sender specific code (if any)
// note: no @XmlRootElement here
public class Person
// data model + jaxb annotations here
[经过测试并确认可以与 JAXB 一起使用]。当继承层次结构中的多个类具有 XmlRootElement 注释时,它会绕过您注意到的问题。
这也可以说是一种更简洁、更面向对象的方法,因为它分离出通用数据模型,因此根本不是“解决方法”。
【讨论】:
它看起来是最好的解决方案,但对我不起作用,也许是因为我的情况有点复杂。我需要转换包含 SenderPerson 和 ReceiverPerson 列表的“组”类的类实例。类似@XmlRootElement public class Group public List创建自定义 ObjectFactory 以在解组期间实例化所需的类。示例:
JAXBContext context = JAXBContext.newInstance("com.whatever.mypackage");
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new ReceiverPersonObjectFactory());
return unmarshaller;
public class ReceiverPersonObjectFactory extends ObjectFactory
public Person createPerson()
return new ReceiverPerson();
【讨论】:
在我看来这是一种非常聪明的方式,因为它只需要创建每个对象一次 - 与 XmlAdpater 解决方案相比,您首先创建绑定类型类实例,然后将所有属性复制到值类实例。但问题是:这是否也适用于不同于 Sun/Oracle 的 JRE,例如 OpenJDK? +1 是我唯一的解决方案。我正在尝试在 xsd 文件中对 JAXB 2.* 中的子类化 (implClass) 使用类型替换,并且未编组的类是生成的类,而不是 implClass 中指定的类。 vocaro的回答解决了问题! 对于 JAXB 是外部实现(不是来自 SDK 的实现)的情况的小更新:该属性称为"com.sun.xml.bind.ObjectFactory"
。【参考方案5】:
我不知道你为什么要这样做……对我来说似乎并不那么安全。
考虑在 ReceiverPerson 中会发生什么有额外的实例变量......然后你会(我猜)这些变量为 null、0 或 false......如果不允许 null 或数字必须是怎么办大于0?
我认为您可能想要做的是在 Person 中读取,然后从中构造一个新的 ReceiverPerson(可能提供一个接受 Person 的构造函数)。
【讨论】:
ReceiverPerson 没有其他变量。它的目的是做接收器特定的东西。 在某种程度上,您仍然需要将其创建为不同的类......而通常的做法是通过构造函数来获取您想要转换的类型。此外,仅仅因为现在是这种情况并不意味着您将来不会添加 var。 哎呀,这是对一个类在未来可能会做什么的猜测。如果它今天足够好,那么它就足够了,你以后可以随时重构...... 公共 API 的消费者喜欢改变......不是。重构对于内部事物来说很好,但任何公开的事物都不是。重构的重点是在不破坏事物的情况下改变幕后工作的方式。如果您正在更改公共 API,那么您不是在重构,而是在重写。【参考方案6】:由于您确实有两个独立的应用程序,因此请使用“Person”类的不同版本编译它们 - 接收器应用程序在 Person
上没有 @XmlRootElement(name="person")
。这不仅丑陋,而且还破坏了对发送者和接收者使用相同的 Person 定义所需的可维护性。它的一个可取之处是它可以工作。
【讨论】:
我希望两个应用程序共享通用的基本 JAXB 类。 @Steve:但我的第二个(“整洁的方式”)解决方案确实共享通用的基本 JAXB 类......看起来你只阅读了第一段,我不清楚我在给两种解决方案。我会编辑。 我把“整洁的方式”分成了一个新的答案。以上是关于JAXB 继承,解组到编组类的子类的主要内容,如果未能解决你的问题,请参考以下文章