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 [...] @Override public ReceiverPerson unmarshal(Person v) return new ReceiverPerson(v); @Override public Person marshal(ReceiverPerson v) return v; 请忽略第一条评论;)它必须也适用于 JAXB 注释类型,为什么不呢? (好吧,我在规范中找不到任何东西)。只需将“extends XmlAdapter”替换为“extends XmlAdapter”和marshalunmarshall,如第二条评论中所述 因为当问题有赏金时,*** 会自动将得票最多的回复作为答案。【参考方案3】:

子类 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 senders; public List 接收者; 【参考方案4】:

创建自定义 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 继承,解组到编组类的子类的主要内容,如果未能解决你的问题,请参考以下文章

JAXB使用CDATA编组解组

在哪里包含 jaxb.properties 文件?

使用 JAXB 解组多次出现的 XML 元素

如何(un)编组子类中根元素的值

如何调试 JAXB 解组?

使用 Xpath 表达式和 jaxb 解组 XML