【问题标题】:XmlID/XmlIDREF and inheritance in latest JAXB version (Java 7)最新 JAXB 版本 (Java 7) 中的 XmlID/XmlIDREF 和继承
【发布时间】:2012-01-03 21:48:40
【问题描述】:

我使用@XmlID 和@XmlIDREF 标记从另一个对象引用一个对象。即使使用继承的类,它在 Java 6 中也能正常工作。我创建的示例代码如下所示。 基类使用的标签:

@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;
    }
}

继承类:

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

    private String feature;

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

这些类的容器:

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

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

运行此示例代码时:

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

我在 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>

请注意,对 m2(TheModule 实例)的引用按预期序列化。 但是当相同的代码在 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>

所以您可以看到在最新的 JAXB 上,继承模块的 @XmlIDREF 不起作用!

【问题讨论】:

    标签: java xml-serialization jaxb java-7


    【解决方案1】:

    这个答案是错误的。不要依赖它,而是阅读 JAXBContext.newInstance(...) 的文档,请参阅 this answer 和下面的 cmets。


    我认为您将 JAXB 与以下行混淆了。

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

    您告诉它您想要序列化 ​​XML 类型 ScriptModuleTheModule。 JAXB 将以特殊 方式处理后一种类型的对象,因为您已经提供了它的基类:它向它添加了一个区分属性。这样可以在序列化的 XML 中区分这两种类型。

    尝试只提供ScriptModule,这是所有 模块的基类。

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

    事实上,您可以完全省略Module。 JAXB 将推断您尝试从上下文序列化的 Script 对象中的类型。

    顺便说一句,这种行为与 Java 6 并不完全相关。它与使用中的 JAXB 实现有关(好吧,几乎是一样的,我知道)。在我的项目中,我使用 JAXB 2.2.4-1,它也重现了 Java 6 和 7 中的问题。

    哦,还有一件事:与其创建 StringWriter 并将对象编组到其中,然后将其内容发送到 System.out,您可以使用以下命令将格式化的 XML 发送到 stdout

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

    也许这可以简化(一点点)进一步的测试。

    【讨论】:

    • 感谢您的回答。我有些疑惑。 JAXBContext.newInstance(Class...) 的 Javadoc 声明调用者必须列出顶级类。 “新上下文将识别所有指定的类,但它还将识别从指定类直接/间接引用的任何类静态(原文如此!)。引用类的子类和@XmlTransient 引用类是未向 JAXBContext 注册。”。如果我们将此语句应用于 Konstantin 的示例,则不会自动发现后代类 TheModule。 \
    • @AndyMalakov 看来你是对的。我不记得我当时是否阅读过newInstance 的文档,当时我回答了这个问题,但我记得我已经尝试了一些代码并且我所写的似乎已经过检查。由于 OP 没有以任何方式回应我的回答,我完全忘记了这一点。实际上我可以看到我误解了实际的问题/问题,所以我的答案完全不正确。我会更新我的答案以反映这一点。感谢您的意见!
    【解决方案2】:

    这似乎是 JAXB 中的一个已知错误。我遇到了同样的问题并通过不使用 XmlIDRef 而是使用带有自定义 ID 的 postDeserialize 来解决它。

    这是一个错误报告:

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

    我无法使用 Kohányi Róbert 的建议。这对你有用吗?

    【讨论】:

      【解决方案3】:

      如果您在 2017 年仍然坚持使用 Java7,那么有一个简单的方法可以规避这一点。问题是当使用 XmlREFId 进行序列化时,Jaxb 无法处理子类。一种解决方案是使用适配器并返回基类的新实例,其 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);
          }
      }
      

      然后,只需在需要的地方使用该适配器:

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

      【讨论】:

        猜你喜欢
        • 2010-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-27
        • 1970-01-01
        • 2011-12-02
        • 1970-01-01
        相关资源
        最近更新 更多