简体   繁体   English

XmlID / XmlIDREF和最新JAXB版本(Java 7)中的继承

[英]XmlID/XmlIDREF and inheritance in latest JAXB version (Java 7)

I am using @XmlID and @XmlIDREF tags to reference one object from another. 我使用@XmlID和@XmlIDREF标记来引用另一个对象。 It was working fine in Java 6 even with inherited classes. 即使使用继承的类,它在Java 6中也能正常工作。 The example code I've created looks like this. 我创建的示例代码如下所示。 Base class used tags: 基类使用的标签:

@XmlRootElement
@XmlAccessorType(FIELD)
public class Module {
    Module() {}

    @XmlIDREF
    private Module other;

    @XmlID
    private String id;

    public Module(String id, Module other) {
        this.id = id;
        this.other = other;
    }
}

Inherited class: 继承类:

@XmlRootElement
public class TheModule extends Module {
    TheModule() {}

    private String feature;

    public TheModule(String id, Module other, String feature) {
        super(id, other);
        this.feature = feature;
    }
}

Container for these classes: 这些类的容器:

@XmlRootElement
public class Script {
    Script() {}
    public Script(Collection<Module> modules) {
        this.modules = modules;
    }

    @XmlElementWrapper
    @XmlElementRef
    Collection<Module> modules = new ArrayList<Module>();
}

When running this example code: 运行此示例代码时:

public class JaxbTest {

    private Script createScript() {
        Module m1 = new Module("Module1", null);
        Module m2 = new TheModule("Module2", m1, "featured  module");
        Module m3 = new Module("Module3", m2);
        return new Script(Arrays.asList(m1, m2, m3));
    }

    private String marshal(Script script) throws Exception {
        JAXBContext context = JAXBContext.newInstance(Module.class, Script.class, TheModule.class);
        Writer writer = new StringWriter();
        context.createMarshaller().marshal(script, writer);
        return writer.toString();
    }

    private void runTest() throws Exception {
        Script script = createScript();

        System.out.println(marshal(script));
    }

    public static void main(String[] args) throws Exception  {
        new JaxbTest().runTest();
    }
}

I receive XML, in Java 6: 我在Java 6中收到XML:

<script>
  <modules>
    <module>
      <id>Module1</id>
    </module>
    <theModule>
      <other>Module1</other>
      <id>Module2</id>
      <feature>featured module</feature>
    </theModule>
    <module>
      <other>Module2</other>
      <id>Module3</id>
    </module>
  </modules>
</script>

Note that reference to the m2 (TheModule instance) is serialized as expected. 请注意,对m2(TheModule实例)的引用按预期序列化。 But when the same code is running under Java 7 (Jaxb 2.2.4-1) I receive: 但是当在Java 7(Jaxb 2.2.4-1)下运行相同的代码时,我收到:

<script>
  <modules>
    <module>
      <id>Module1</id>
    </module>
    <theModule>
      <other>Module1</other>
      <id>Module2</id>
      <feature>featured module</feature>
    </theModule>
    <module>
      <other xsi:type="theModule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <other>Module1</other>
        <id>Module2</id>
        <feature>featured module</feature>
      </other>
      <id>Module3</id>
    </module>
  </modules>
</script>

So you can see that on latest JAXB @XmlIDREF for inherited module is not working! 所以你可以看到最新的JAXB @XmlIDREF继承模块不起作用!

This answer is wrong . 这个答案是错的 Instead of relying on it read the documentation for JAXBContext.newInstance(...) , see this answer and the comments below. 而不是依赖它阅读JAXBContext.newInstance(...)的文档,请参阅此答案和下面的评论。


I think you confuse JAXB with the following line. 我认为你把JAXB与以下行混淆了。

JAXBContext.newInstance(Module.class, Script.class, TheModule.class);

You tell it that you want to serialize the XML types Script , Module and TheModule . 您告诉它您要序列化XML类型ScriptModuleTheModule JAXB will treat objects of this latter type in a special way, because you've already supplied its base class: it adds a discriminating attribute to it. JAXB将以特殊方式处理后一种类型的对象,因为您已经提供了它的基类:它为它添加了一个区别属性。 This way these two types can be differentiated in the serialized XML. 这样,这两种类型可以在序列化XML中区分。

Try supplying only Script and Module , the base class for all modules. 尝试仅提供ScriptModule ,这是所有模块的基类。

JAXBContext.newInstance(Module.class, Script.class);

In fact, you can leave out Module entirely. 实际上,您可以完全省略Module JAXB will infer types in the Script object you try to serialize from the context. JAXB将推断您尝试从上下文序列化的Script对象中的类型。

This behaviour by the way isn't exactly related to Java 6. It's related to the JAXB implementation in use (okay-okay, almost the same thing, I know). 顺便说一下,这种行为与Java 6并不完全相关。它与正在使用的JAXB实现有关(好吧 - 好吧,差不多,我知道)。 In my projects I use JAXB 2.2.4-1, which reproduces the issue at hand in Java 6 and 7 too. 在我的项目中,我使用JAXB 2.2.4-1,它也在Java 6和7中重现了手头的问题。

Oh, and one more thing: instead of creating a StringWriter and marshalling objects into that, then sending its contents to System.out you can use the following to send formatted XML to stdout . 哦,还有一件事:不是创建一个StringWriter并将对象编组到其中,然后将其内容发送到System.out您可以使用以下命令将格式化的XML发送到stdout

Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(script, System.out);

Maybe this can ease (a tiny little bit) further testing. 也许这可以轻松(一点点)进一步测试。

This seems to be a known bug in JAXB. 这似乎是JAXB中的一个已知错误。 I had the same problem and resolved it by not using XmlIDRef, but rather using postDeserialize with a custom ID. 我遇到了同样的问题并通过不使用XmlIDRef来解决它,而是使用带有自定义ID的postDeserialize。

Here is a bug report: 这是一个错误报告:

http://java.net/jira/browse/JAXB-870 http://java.net/jira/browse/JAXB-870

I couldn't use Kohányi Róbert's suggestion. 我无法使用KohányiRóbert的建议。 Did that work for you? 这对你有用吗?

If you are still stuck with Java7 in 2017, then there is an easy way to circumvent this. 如果你在2017年仍然坚持使用Java7,那么有一种简单的方法可以避免这种情况。 The problem is that when serialiazing with a XmlREFId, Jaxb can't cope with sub-classes. 问题是当使用XmlREFId进行序列化时,Jaxb无法处理子类。 A solution is to use an adapter and return an new instance of the base class with the same id as the one being serialized (only the id will be used, so we don't need to bother with the other fields): 一个解决方案是使用一个适配器并返回一个基类的新实例,其id与被序列化的id相同(只使用id,所以我们不需要打扰其他字段):

public class ModuleXmlAdapter extends XmlAdapter<Module, Module> {
    @Override
    public Module unmarshal(Module v) {
        // there is no problem with deserializing - return as is
        return v;
    }

    @Override
    public Module marshal(Module v) {
        if (v == null) {
            return null;
        }
        // here is the trick:
        return new Module(v.getId(), null);
    }
}

Then, just use that adapter wherever needed: 然后,只需在任何需要的地方使用该适配器:

public class Module {
    @XmlIDREF
    @XmlJavaTypeAdapter(ModuleXmlAdapter.class)
    private Module other;

    //...
}

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

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