【问题标题】:JAXB inheritance, unmarshal to subclass of marshaled classJAXB 继承,解组到编组类的子类
【发布时间】:2010-10-11 19:26:29
【问题描述】:

我正在使用 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,但这无济于事。

【问题讨论】:

    标签: java xml jaxb


    【解决方案1】:

    下面的sn-p是一个绿灯的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(因此它们必须按此顺序传递)。

    【讨论】:

    • 这个实际上效果最好,如果您可以为类使用 JAXB 注释,则不需要适配器。
    【解决方案2】:

    您使用的是 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] 类型)。

    【讨论】:

    • 我试过了,但它不起作用。无论我尝试什么,ObjectFactory 或我的 XmlAdapter 都不会被调用。从我所读到的 Sun 的 JAXB 通过静态类引用解析。 Glassfish 的实现似乎更有希望jaxb.dev.java.net/guide/Adding_behaviors.html
    • XmlJavaAdapters 适合使非 JAXB 注释的类可用于 JAXB。因为 Person 和 ReceiverPerson(和/或他们的 superlass,如果有的话)有 JAXB 注释,所以它不起作用。您需要没有 JAXB 的 Person 和 ReceiverPerson,以及两者的适配器。
    • 您是否尝试过为 marshall|unmarshall 交换类型? [...] 扩展 XmlAdapter [...] @Override public ReceiverPerson unmarshal(Person v){ return new ReceiverPerson(v); } @Override public Person marshal(ReceiverPerson v){ return v; }
    • 请忽略第一条评论;)它必须也适用于 JAXB 注释类型,为什么不呢? (好吧,我在规范中找不到任何东西)。只需将“extends XmlAdapter”替换为“extends XmlAdapter”和marshalunmarshall,如第二条评论中所述
    • 因为当问题有赏金时,Stackoverflow 会自动将得票最多的回复作为答案。
    【解决方案3】:

    子类 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 注释时,它会绕过您注意到的问题。

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

    【讨论】:

    • 它看起来是最好的解决方案,但对我不起作用,也许是因为我的情况有点复杂。我需要转换包含 SenderPerson 和 ReceiverPerson 列表的“组”类的类实例。类似@XmlRootElement public class Group { public List senders; public List 接收者; }
    【解决方案4】:

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

    【讨论】:

    • 在我看来这是一种非常聪明的方式,因为它只需要创建每个对象一次 - 与 XmlAdpater 解决方案相比,您首先创建绑定类型类实例,然后将所有属性复制到值类实例。但问题是:这是否也适用于不同于 Sun/Oracle 的 JRE,例如 OpenJDK?
    • +1 是我唯一的解决方案。我正在尝试在 xsd 文件中对 JAXB 2.* 中的子类化 (implClass) 使用类型替换,并且未编组的类是生成的类,而不是 implClass 中指定的类。 vocaro的回答解决了问题!
    • 对于 JAXB 是外部实现(不是来自 SDK 的实现)的情况的小更新:该属性称为 "com.sun.xml.bind.ObjectFactory"
    【解决方案5】:

    我不知道你为什么要这样做……对我来说似乎并不那么安全。

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

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

    【讨论】:

    • ReceiverPerson 没有其他变量。它的目的是做接收器特定的东西。
    • 在某种程度上,您仍然需要将其创建为不同的类......而通常的做法是通过构造函数来获取您想要转换的类型。此外,仅仅因为现在是这种情况并不意味着您将来不会添加 var。
    • 哎呀,这是对一个类在未来可能会做什么的猜测。如果它今天足够好,那么它就足够了,你以后可以随时重构......
    • 公共 API 的消费者喜欢改变......不是。重构对于内部事物来说很好,但任何公开的事物都不是。重构的重点是在不破坏事物的情况下改变幕后工作的方式。如果您正在更改公共 API,那么您不是在重构,而是在重写。
    【解决方案6】:

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

    【讨论】:

    • 我希望两个应用程序共享通用的基本 JAXB 类。
    • @Steve:但我的第二个(“整洁的方式”)解决方案确实共享通用的基本 JAXB 类......看起来你只阅读了第一段,我不清楚我在给两种解决方案。我会编辑。
    • 我把“整洁的方式”分成了一个新的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-26
    • 1970-01-01
    • 2011-07-03
    • 1970-01-01
    • 2010-12-28
    相关资源
    最近更新 更多