我正在使用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
,但这没有帮助。
答案 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
对其进行注释,这使DummyPerson
和ReceiverPerson
处于同一级别,然后可以封送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();
}
答案 6 :(得分:-1)
由于您确实有两个独立的应用程序,因此使用“Person”类的不同版本编译它们 - 接收方应用程序@XmlRootElement(name="person")
上没有Person
。这不仅是丑陋的,而且它对发送者和接收者使用相同的Person定义也会破坏你想要的可维护性。它的一个救赎功能是它有效。