45

我正在使用 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() ...
}

编组按预期工作。问题在于解组,Person尽管 JAXBContext 使用 subclassed 的包名称,它仍然解组ReceiverPerson

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

我想要的是解组到ReceiverPerson. 我能够做到这一点的唯一方法是@XmlRootElementPerson. 不幸的是,这样做可以防止Person被编组。就好像 JAXB 从基类开始并向下工作,直到找到@XmlRootElement具有适当名称的第一个。我试过添加一个createPerson()返回的方法,ReceiverPerson但这ObjectFactory没有帮助。

4

6 回答 6

21

下面的代码片段是一个带绿灯的 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(所以它们必须按这个顺序传递)。

于 2009-03-18T03:23:48.220 回答
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] 类型)。

于 2009-03-11T22:14:23.467 回答
13

子类 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 注释时,它可以避免您注意到的问题。

这也可以说是一种更简洁、更面向对象的方法,因为它分离了通用数据模型,所以它根本不是一个“解决方法”。

于 2009-03-18T09:15:48.980 回答
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();
    }
}
于 2010-12-01T05:24:09.487 回答
3

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

考虑在 ReceiverPerson 中会发生什么有额外的实例变量......然后你会结束(我猜)这些变量是 null、0 或 false......如果不允许 null 或数字必须大于 0 怎么办?

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

于 2009-03-06T18:42:00.727 回答
-1

由于您确实有两个单独的应用程序,因此请使用“Person”类的不同版本编译它们 - 接收器应用程序没有@XmlRootElement(name="person"). Person这不仅丑陋,而且还破坏了对发送者和接收者使用相同的 Person 定义所需的可维护性。它的一个可取之处是它可以工作。

于 2009-03-17T09:26:51.497 回答