2011-11-22 73 views
5

我使用@XmlID和@XmlIDREF標記來引用另一個對象。即使使用繼承的類,它在Java 6中也能正常工作。我創建的示例代碼如下所示。 基類中使用標籤:XmlID/XmlIDREF和最新JAXB版本(Java 7)中的繼承

@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 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爲繼承模塊不能正常工作!

回答

2

這個答案是錯了。不要依賴它,請閱讀JAXBContext.newInstance(...)的文檔,請參閱this answer以及下面的註釋。


我想你會混淆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); 

也許這可以緩解(一點點)進一步的測試。

+0

謝謝您的回答。我有些疑惑。 JAXBContext.newInstance(Class ...)的Javadoc聲明調用者必須列出頂級類。 「新的上下文將識別指定的所有類,但它也將識別任何直接/間接引用的類**從指定的類靜態**(sic!)。被引用的類的子類和@XmlTransient引用的類未註冊JAXBContext中。「 如果我們將此聲明康斯坦丁的例子,子類TheModule不會被自動發現。 \ –

+0

@AndyMalakov看起來你是對的。當我回答這個問題時,我不記得是否已經閱讀過'newInstance'的文檔,但我記得我已經嘗試了一些代碼,並且我寫的內容似乎已經被檢查出來。由於OP沒有以任何方式迴應我的回答,所以我完全忘記了這一點。其實我可以看到我錯誤地解釋了實際的問題/問題,所以我的答案完全沒有了。我會更新我的答案以反映這一點。感謝您的輸入! –

0

這似乎是在JAXB一個已知的bug。我遇到了同樣的問題,通過不使用XmlIDRef來解決問題,而是使用帶有自定義ID的postDeserialize。

這是一個錯誤報告:

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

我不能使用Kohányi羅伯特的建議。這對你有用嗎?

0

如果你還是堅持了Java7在2017年,則有繞過這個簡單的方法。問題是,當使用XmlREFId串行化時,Jaxb無法處理子類。一種解決方法是使用一個適配器和作爲一個被序列化具有相同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; 

    //... 
}