2017-02-17 97 views
0

當第一次解組,然後編組對象時,當前對象具有@XmlAnyElement屬性時,我正面臨一個奇怪的JAXB名稱空間行爲。避免anyElement上重複的名稱空間定義

這裏的設置:

package-info.java

@XmlSchema(
    namespace = "http://www.example.org", 
    elementFormDefault = XmlNsForm.QUALIFIED, 
    xmlns = { @javax.xml.bind.annotation.XmlNs(prefix = "example", namespaceURI = "http://www.example.org") } 
) 

類型定義:

@XmlRootElement 
@XmlType(namespace="http://www.example.org") 
public class Message { 

    private String id; 

    @XmlAnyElement(lax = true) 
    private List<Object> any; 

    public String getId() { 
     return id; 
    } 

    public void setId(String id) { 
     this.id = id; 
    } 

    public List<Object> getAny() { 
     if (any == null) { 
      any = new ArrayList<>(); 
     } 
     return this.any; 
    } 
} 

和測試代碼本身:

@Test 
public void simpleTest() throws JAXBException { 

    JAXBContext jaxbContext = JAXBContext.newInstance(Message.class); 
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); 
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); 
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 

    String xml = 
      "<example:message xmlns:example=\"http://www.example.org\" xmlns:test=\"http://www.test.org\" xmlns:unused=\"http://www.unused.org\">\n" + 
      " <example:id>id-1</example:id>\n" + 
      " <test:value>my-value</test:value>\n" + 
      " <test:value>my-value2</test:value>\n" + 
      "</example:message>"; 
    System.out.println("Source:\n"+xml); 

    // parsed 
    Object unmarshalled = unmarshaller.unmarshal(new StringReader(xml)); 

    // directly convert it back 
    StringWriter writer = new StringWriter(); 
    marshaller.marshal(unmarshalled, writer); 
    System.out.println("\n\nMarshalled again:\n"+writer.toString()); 
} 

的問題這個設置是將所有「未知」名稱空間重複添加到任何元素。

<example:message xmlns:example="http://www.example.org" xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org"> 
    <example:id>id-1</example:id> 
    <test:value>my-value</test:value> 
    <test:value>my-value2</test:value> 
</example:message> 

變成這樣:

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value</test:value> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value2</test:value> 
    <example:id>id-1</example:id> 
</example:message> 

因此,我怎樣才能避免這種情況!爲什麼不像在輸入xml中一樣在根元素中定義一次命名空間?由於anyElement的命名空間並不是先前已知的,因此無法通過程序包定義註冊它...

此外,是否還有可能將未使用的命名空間剝離(按需)?

回答

1

當JAXB開始將對象編組爲XML時,它將根據對象層次結構中的何處以及輸出XML來獲取一些上下文。這是一個定義上的流式操作,所以它只會查看當前和當前上下文中正在發生的事情。

所以說它開始編組你的Message實例。它將檢查本地元素名稱應該是什麼(message),它必須位於的名稱空間(http://www.example.org)以及是否有與該名稱空間綁定的特定前綴(在您的情況中,是,前綴example)。只要你在你的Message實例中,那現在就是上下文的一部分。如果它在同一個命名空間內遇到層次結構中的其他對象,它將已經在它的上下文中並重新使用相同的前綴,因爲它知道某個父元素或祖先元素已聲明瞭它。它還檢查是否有任何屬性需要編組,因此它可以完成開始標記。 XML輸出到目前爲止是這樣的:

<example:message xmlns:example="http://www.example.org"> 

現在它開始挖掘到了必須編組但不是屬性的字段。它找到您的List<Object> any字段並開始工作。第一個條目是一些將編組到名稱空間爲http://www.test.org中的value元素的對象。該名稱空間在當前上下文中尚未綁定到任何前綴,因此它被添加,並且通過package-info註釋(或其他支持的方法)找到首選前綴。有沒有什麼可以再嵌套在需要進行編組值的,所以它可以完成的那部分,現在輸出看起來是這樣的:

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value</test:value> 

這裏的第一個列表項結束的編組,價值元素獲得其結束標記及其上下文到期。到下一個列表條目。它又是一個對象的實例,它被編組到value,同樣在同一個命名空間中,但它在當前上下文中不再具有該實例。所以同樣的事情發生。

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value</test:value> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value2</test:value> 

現在得到繞到String id領域,相同的命名空間內留言落在。在當前情況下,這仍然是衆所周知的,因爲我們仍然留言。這樣該名稱空間不會再次聲明。

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value</test:value> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value2</test:value> 
    <example:id>id-1</example:id> 
</example:message> 

那麼,爲什麼不只是JAXB維護名稱空間和它們的前綴綁定的列表,並把那些在根元素?因爲它是流式輸出。它不能只是跳回來。它可以,如果它在內存中構建DOM,但效率不高。相反,爲什麼它不是隻是先遍歷它的對象樹並創建一個要使用的名稱空間綁定列表?再一次,因爲那不會很有效率。另外,它可能並不完全知道在處理過程中上下文將如何改變。也許我們最終會得到一些包含不同名稱空間的包,但與其他某些名稱空間的前綴相同。如果在XML中我們目前沒有綁定任何該前綴,那很好。像這裏(注意第二個測試的命名空間):

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org">my-value</test:value> 
    <test:value xmlns:test="http://completelydifferenttest">my-value2</test:value> 
    <example:id>id-1</example:id> 
</example:message> 

但在其他情況下,它會選擇一些不同的前綴。像這樣的語義等價文件:

<example:message xmlns:example="http://www.example.org" xmlns:test="http://www.test.org"> 
    <test:value>my-value</test:value> 
    <ns1:value xmlns:ns1="http://completelydifferenttest">my-value2</ns1:value> 
    <example:id>id-1</example:id> 
</example:message> 

因此,JAXB只是在當前包含的上下文中,並在本地查看事物。它不會在前面進行挖掘。

但是,這並不能真正解決問題。所以這是你可以做的。

  • 忽略它。儘管可能是冗長而醜陋的輸出結果是正確的。
  • 在編組之後應用XSLT轉換來清理名稱空間。
  • 使用自定義NamespacePrefixMapper。
  • 對XMLEventWriter進行編組,並讓它將自定義事件委託給標準編寫器。

自定義映射器是依賴於JAXB參考實現並使用內部類的解決方案。所以它的向前兼容性不能得到真正的保證。 Blaise Doughan解釋了它在這個答案中的用法:https://stackoverflow.com/a/28540700/630136

最後一個選項有一點涉及。你可以編寫一些事件編寫器,它輸出所有名稱空間在根元素上的默認前綴綁定,並且當它是一個已知的命名空間時,在隨後的元素上忽略它們。您從一開始就有效地保留了一些全球背景。

XSLT可能是最簡單的,儘管它可能需要一些試驗來了解XSLT處理器如何處理它。這實際上是一個做了把戲對我來說:

<?xml version="1.0" encoding="UTF-8" ?> 
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" 
    xmlns:example="http://www.example.org" xmlns:test="http://www.test.org" 
    xmlns:unused="http://www.unused.org"> 
    <xsl:output method="xml" indent="yes" /> 

    <xsl:template match="node()|@*"> 
     <xsl:copy> 
      <xsl:apply-templates select="node()|@*" /> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="/example:message"> 
     <example:message> 
      <xsl:apply-templates select="node()|@*" /> 
     </example:message> 
    </xsl:template> 

</xsl:transform> 

需要注意的是,如果我把第二個模板在比賽上/*並使用<xsl:copy>方法有,它在某種程度上是行不通的。

要從一個對象編組並且以一個平滑的步驟轉換生成的XML,請查看the JAXBSource class的使用。它允許您使用JAXB對象作爲XML轉換的源。

編輯:關於「未使用」的命名空間。我記得在某些時候得到了一些在某些JAXB輸出中甚至不需要的命名空間,在這種情況下,它證明與由XML到Java編譯器I在某些類上放置的@XmlSeeAlso註釋有關正在使用(起點是一個XML模式)。註釋可以確保如果一個類被加載到JAXBContext中,則包含@XmlSeeAlso中引用的類。這可以使上下文的創建更容易。但是一個副作用是它包含了一些我並不總是需要的東西,並且在上下文中並不總是需要。我認爲JAXB將爲它在此時可以找到的所有內容創建名稱空間前綴映射。

說起來,這實際上可以爲您的問題提供另一種解決方案。如果您在根類中添加@XmlSeeAlso註釋,並引用其他可能可能使用的類(或至少是子層次結構的根),那麼JAXB可能已經將所遇到的包的所有名稱空間綁定到了根上。我並不總是註解的粉絲,因爲我不認爲超類應該引用實現,並且層次結構中更高層的類不應該擔心其中較低層的細節。但如果它與你的架構不衝突,那就值得一試。

+0

哇。很好的答案。謝謝! XSLT方法看起來很誘人,但我認爲與未知名稱空間一起,這可能會變得非常難看......自定義映射器會很好,但不幸的是我沒有使用參考實現(僅僅是痛苦的JDK沒有縮影)。因此我認爲我必須忍受醜陋。 *一個*最後一個問題:爲什麼在步驟2中添加了「未使用的」NS?不需要......編組人員只是添加所有的_remaining_命名空間,因爲它不知道會發生什麼? – Ingo

+0

@Ingo如果您使用的是標準的Java發行版,那麼您可能已經使用了參考實現。當然,爲了真正能夠在內部命名空間中導入類,您需要一些依賴性,但可以將其視爲「提供」。至於「未使用」的命名空間,我知道我忘了一些東西!編輯即將到來。 –

+0

再次......感謝您的快速響應和更新。如您所見,我沒有任何'@ XmlSeeAlso'參考,因此,我不知道爲什麼會發生這種情況。 any元素實際上是一個擴展,因此其他人可以自己添加內容。沒有模式,沒有一代,沒有。只是一個擴展點。好的...然後_they_應該只能發送他們真正在xml中使用的名稱空間聲明。沒有從我們這邊清理。 ;-) – Ingo