首页 文章

JAXB继承,unmarshal到marshaled类的子类

提问于
浏览
40

我正在使用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使用了子类 ReceiverPerson 的包名,但仍然可以解组 Person .

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

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

6 回答

  • 7

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

  • 12

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

  • -1

    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

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

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

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

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

  • 20

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

相关问题