Java JAXB 继承,解组到编组类的子类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/619761/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:02:09  来源:igfitidea点击:

JAXB inheritance, unmarshal to subclass of marshaled class

javaxmljaxb

提问by Steve Kuo

I'm using JAXB to read and write XML. What I want is to use a base JAXB class for marshalling and an inherited JAXB class for unmarshalling. This is to allow a sender Java application to send XML to another receiver Java application. The sender and receiver will share a common JAXB library. I want the receiver to unmarshall the XML into a receiver specific JAXB class which extends the generic JAXB class.

我正在使用 JAXB 来读取和写入 XML。我想要的是使用基本 JAXB 类进行编组,使用继承的 JAXB 类进行解组。这是为了允许发送方 Java 应用程序将 XML 发送到另一个接收方 Java 应用程序。发送方和接收方将共享一个公共 JAXB 库。我希望接收器将 XML 解组为扩展通用 JAXB 类的接收器特定 JAXB 类。

Example:

例子:

This is the common JAXB class which is used by the sender.

这是发送方使用的通用 JAXB 类。

@XmlRootElement(name="person")
public class Person {
    public String name;
    public int age;
}

This is the receiver specific JAXB class used when unmarshalling the XML. The receiver class has logic specific to the receiver application.

这是在解组 XML 时使用的特定于接收器的 JAXB 类。接收器类具有特定于接收器应用程序的逻辑。

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

Marshalling works as expected. The problem is with unmarshalling, it still unmarshals to Persondespite the JAXBContext using the package name of the subclassed ReceiverPerson.

编组按预期工作。问题在于解组,Person尽管 JAXBContext 使用了子类ReceiverPerson.

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

What I want is to unmarshall to ReceiverPerson. The only way I've been able to do this is to remove @XmlRootElementfrom Person. Unfortunately doing this prevents Personfrom being marshaled. It's as if JAXB starts at the base class and works its way down until it finds the first @XmlRootElementwith the appropriate name. I've tried adding a createPerson()method which returns ReceiverPersonto ObjectFactorybut that doesn't help.

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

采纳答案by ivan_ivanovich_ivanoff

You're using JAXB 2.0 right? (since JDK6)

您使用的是 JAXB 2.0 对吗?(从 JDK6 开始)

There is a class:

有一个类:

javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>

which one can subclass, and override following methods:

哪个可以子类化,并覆盖以下方法:

public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;

Example:

例子:

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
    }
}

Usage is done by as following:

使用方法如下:

@Your_favorite_JAXB_Annotations_Go_Here
class SomeClass{
    @XmlJavaTypeAdapter(YourNiceAdapter.class)
    Person hello; // field to unmarshal
}

I'm pretty sure, by using this concept you can control the marshalling/unmarshalling process by yourself (including the choice the correct [sub|super]type to construct).

我很确定,通过使用这个概念,您可以自己控制编组/解组过程(包括选择正确的 [sub|super] 类型来构造)。

回答by TofuBeer

I am not sure why you would want to do this... it doesn't seem all that safe to me.

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

Consider what would happen in ReceiverPerson has additional instance variables... then you would wind up with (I guess) those variables being null, 0, or false... and what if null is not allowed or the number must be greater than 0?

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

I think what you probably want to do is read in the Person and then construct a new ReceiverPerson from that (probably provide a constructor that takes a Person).

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

回答by 13ren

Since you really have two separate apps, compile them with different versions of the class "Person" - with the receiver app not having @XmlRootElement(name="person")on Person. Not only is this ugly, but it defeats the maintainability you wanted from using the same definition of Person for both sender and receiver. Its one redeeming feature is that it works.

由于您确实有两个独立的应用程序,请使用不同版本的“Person”类编译它们 - 接收器应用程序没有@XmlRootElement(name="person")打开Person. 这不仅丑陋,而且还破坏了您对发送者和接收者使用相同的 Person 定义所希望的可维护性。它的一个赎回功能是它的工作原理。

回答by Pascal Thivent

The following snippet is a method of a Junit 4 test with a green light:

以下代码段是一个带有绿灯的 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);
}

The important part is the use of the JAXBContext.newInstance(Class... classesToBeBound)method for the unmarshalling context:

重要的部分是将JAXBContext.newInstance(Class... classesToBeBound)方法用于解组上下文:

 context = JAXBContext.newInstance(Person.class, RecieverPerson.class);

With this call, JAXB will compute a reference closure on the classes specified and will recognize RecieverPerson. The test passes. And if you change the parameters order, you'll get a java.lang.ClassCastException(so they mustbe passed in this order).

通过此调用,JAXB 将计算指定类的引用闭包,并将识别RecieverPerson. 测试通过。如果你改变参数顺序,你会得到一个java.lang.ClassCastException(所以它们必须按这个顺序传递)。

回答by 13ren

Subclass Person twice, once for receiver and once for sender, and only put the XmlRootElement on these subclassses (leaving the superclass, Person, without an XmlRootElement). Note that sender and receiver both share the same JAXB base classes.

将 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
}

[tested and confirmed to work with JAXB]. It circumvents the problem you note, when multiple classes in the inheritance hierarchy have the XmlRootElement annotation.

[经过测试并确认可与 JAXB 一起使用]。当继承层次结构中的多个类具有 XmlRootElement 注释时,它可以避免您注意到的问题。

This is arguably also a neater and more OO approach, because it separates out the common data model, so it's not a "workaround" at all.

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

回答by vocaro

Create a custom ObjectFactory to instantiate the desired class during unmarshalling. Example:

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

回答by Jason Law

The key point is "when unmarshalling, JAXB starts at the base class and works its way down", although you specify ReceiverPersonwhen unmarshalling, you have class Personannotated with @XmlRootElement, so it will unmarshal to Person.

关键是“在解组时,JAXB 从基类开始并向下工作”,尽管您ReceiverPerson在解组时指定了,但您已用Person注释了类@XmlRootElement,因此它将解组为Person

So you need to remove @XmlRootElementat class Person, but doing this prevents Personfrom being marshaled.

所以你需要@XmlRootElement在 class删除Person,但这样做可以防止Person被封送。

The solution is to create a DummyPersonwhich extends Person and annotate it with @XmlRootElement, this makes DummyPersonand ReceiverPersonon the same level, then you can marshal DummyPersoninstead of Personand unmarshal xmlString to ReceiverPerson.

解决方案是创建一个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();
}


References:
Inheritance Support in JAXB

参考资料:
JAXB 中的继承支持