JAXB继承,unmarshal到marshaled类的子类

时间:2009-03-06 17:47:00

标签: java xml jaxb

我正在使用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使用了子类Person的包名称,但它仍然是ReceiverPerson的解组。

JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson);

我想要的是解散ReceiverPerson。我能够做到这一点的唯一方法是从@XmlRootElement中删除Person。不幸的是,这样做会阻止Person被编组。这就好像JAXB从基类开始并向下运行,直到它找到具有适当名称的第一个@XmlRootElement。我尝试添加createPerson()方法,将ReceiverPerson返回ObjectFactory,但这没有帮助。

7 个答案:

答案 0 :(得分:20)

你正在使用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]类型)。

答案 1 :(得分:19)

以下代码段是使用绿灯进行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(因此必须按此顺序传递)。

答案 2 :(得分:12)

Subclass Person两次,一次用于receiver,一次用于sender,只将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批注时,它会避免您注意到的问题。

这可以说是一种更简洁,更实用的方法,因为它将常见的数据模型分开,因此它根本不是“解决方法”。

答案 3 :(得分:7)

创建自定义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();
    }
}

答案 4 :(得分:3)

我不确定你为什么要这样做......对我来说似乎并不安全。

考虑在ReceiverPerson中会发生什么会有额外的实例变量...那么你最终会得到(我猜)那些变量为null,0或false ......以及如果不允许null或者数字必须是大于0?

我认为您可能想要做的是在Person中读取然后从中构造一个新的ReceiverPerson(可能提供一个带有Person的构造函数)。

答案 5 :(得分:0)

关键点是“解组时,JAXB从基类开始并向下运行”,尽管您在解组时指定了ReceiverPerson,但类Person带有@XmlRootElement注释,因此它将解组到Person

因此,您需要在@XmlRootElement类中删除Person,但这可以防止Person被封送。

解决方案是创建一个DummyPerson来扩展Person并用@XmlRootElement对其进行注释,这使DummyPersonReceiverPerson处于同一级别,然后可以封送DummyPerson而不是Person,然后将xmlString封送至ReceiverPerson

@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
    public String name;
    public int age;
}

@XmlRootElement(name = "person")
public class DummyPerson extends Person {
}

@XmlRootElement(name = "person")
public class ReceiverPerson extends Person {
     public doReceiverSpecificStuff();
}

参考文献:
Inheritance Support in JAXB

答案 6 :(得分:-1)

由于您确实有两个独立的应用程序,因此使用“Person”类的不同版本编译它们 - 接收方应用程序@XmlRootElement(name="person")上没有Person。这不仅是丑陋的,而且它对发送者和接收者使用相同的Person定义也会破坏你想要的可维护性。它的一个救赎功能是它有效。