简体   繁体   English

JAXB继承,unmarshal到marshaled类的子类

[英]JAXB inheritance, unmarshal to subclass of marshaled class

I'm using JAXB to read and write XML. 我正在使用JAXB来读写XML。 What I want is to use a base JAXB class for marshalling and an inherited JAXB class for unmarshalling. 我想要的是使用基本JAXB类进行编组,并使用继承的JAXB类进行解组。 This is to allow a sender Java application to send XML to another receiver Java application. 这是为了允许发送方Java应用程序将XML发送到另一个接收方Java应用程序。 The sender and receiver will share a common JAXB library. 发送方和接收方将共享一个通用的JAXB库。 I want the receiver to unmarshall the XML into a receiver specific JAXB class which extends the generic JAXB class. 我希望接收器将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. 这是解组XML时使用的特定于接收器的JAXB类。 The receiver class has logic specific to the receiver application. 接收器类具有特定于接收器应用的逻辑。

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

Marshalling works as expected. 编组按预期工作。 The problem is with unmarshalling, it still unmarshals to Person despite 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 . 我想要的是解密到ReceiverPerson The only way I've been able to do this is to remove @XmlRootElement from Person . 我能够做到这一点的唯一方法是从Person删除@XmlRootElement Unfortunately doing this prevents Person from being marshaled. 不幸的是,这样做可以防止Person被编组。 It's as if JAXB starts at the base class and works its way down until it finds the first @XmlRootElement with the appropriate name. 这就好像JAXB从基类开始并向下运行,直到它找到第一个具有适当名称的@XmlRootElement I've tried adding a createPerson() method which returns ReceiverPerson to ObjectFactory but that doesn't help. 我已经尝试添加一个createPerson()方法,它将ReceiverPerson返回给ObjectFactory但这没有帮助。

You're using JAXB 2.0 right? 你正在使用JAXB 2.0吗? (since JDK6) (自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]类型)。

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 . 通过此调用,JAXB将计算指定类的引用闭包,并将识别RecieverPerson The test passes. 测试通过。 And if you change the parameters order, you'll get a java.lang.ClassCastException (so they must be passed in this order). 如果更改参数顺序,则会得到java.lang.ClassCastException (因此必须按此顺序传递)。

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). Subclass Person两次,一次用于receiver,一次用于sender,只将XmlRootElement放在这些子类上(留下超类Person ,没有XmlRootElement)。 Note that sender and receiver both share the same JAXB base classes. 请注意,发送方和接收方都共享相同的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]. [经过测试并确认可与JAXB合作]。 It circumvents the problem you note, when multiple classes in the inheritance hierarchy have the XmlRootElement annotation. 当继承层次结构中的多个类具有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. 这可以说是一种更简洁,更实用的方法,因为它将常见的数据模型分开,所以它根本不是“解决方法”。

Create a custom ObjectFactory to instantiate the desired class during unmarshalling. 创建一个自定义ObjectFactory以在解组期间实例化所需的类。 Example: 例:

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();
    }
}

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会发生什么情况会有额外的实例变量......那么你最终会得到(我猜)那些变量为null,0或false ......以及如果不允许null或者数字必须大于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的构造函数)。

The key point is "when unmarshalling, JAXB starts at the base class and works its way down", although you specify ReceiverPerson when unmarshalling, you have class Person annotated with @XmlRootElement , so it will unmarshal to Person . 关键点是“当解组时,JAXB从基类开始并向下运行”,尽管在解组时指定了ReceiverPerson ,但是你有使用@XmlRootElement注释的类Person ,因此它将解组为Person

So you need to remove @XmlRootElement at class Person , but doing this prevents Person from being marshaled. 因此,您需要在Person类中删除@XmlRootElement ,但这样做会阻止Person被封送。

The solution is to create a DummyPerson which extends Person and annotate it with @XmlRootElement , this makes DummyPerson and ReceiverPerson on the same level , then you can marshal DummyPerson instead of Person and unmarshal xmlString to ReceiverPerson . 该解决方案是创建一个DummyPerson延伸的人与一个注释它@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中的继承支持

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 . 由于您确实有两个单独的应用程序,因此使用“Person”类的不同版本编译它们 - 接收器应用程序在Person上没有@XmlRootElement(name="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. 这不仅是丑陋的,而且它对发送者和接收者使用相同的Person定义都会破坏你想要的可维护性。 Its one redeeming feature is that it works. 它的一个救赎功能是它有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM