2012-08-10 100 views
5

我的問題是非常相似的How to prevent marshalling empty tags in JAXB when string is empty but not nullJAXB元帥空字符串空全球

不同的是,我無法添加註解的package-info.java從構造每個模式都產生所有的JAXB類型。 如果可能的話,我也不會更改JAXB提供程序。

我想要實現的是設置一個空字符串不會創建元素,但我需要爲來自多個模式的所有生成的JAXB類型設置此值。有沒有辦法將此應用於所有生成的JAXB類中的所有字符串字段?

更新 我已經設法獲得通過以下的改變模式中的所有字符串XML適配器生成:

在項目POM,我已將此添加到Maven的JAXB2-插件:

<bindingDirectory>src/main/resources</bindingDirectory> 
<bindingIncludes> 
    <include>bindings.xjb</include> 
</bindingIncludes> 

這裏是我的bindings.xjb文件:

<jxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb" version="2.1"> 
    <jxb:globalBindings> 
        <jxb:javaType name="java.lang.String" xmlType="xs:token" 
         parseMethod="com.project.Formatter.parseString" 
         printMethod="com.project.Formatter.printString"/> 
    </jxb:globalBindings> 
</jxb:bindings> 

而且格式方法:

public static String printString(final String value) 
{ 
    if (StringUtils.isBlank(value)) 
    { 
     return null; 
    } 

    return value; 
} 

問題是,這會導致JAXB內深空指針異常。這裏是堆棧跟蹤:

Caused by: java.lang.NullPointerException 
    at com.sun.xml.bind.v2.runtime.output.SAXOutput.text(SAXOutput.java:158) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:321) 
    at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:210) 
    at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:209) 
    at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:250) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:98) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65) 
    at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168) 
    at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:152) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:156) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:185) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:305) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:312) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:71) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:490) 
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:328) 
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:257) 
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:103) 

這個問題的原因歸結爲這種方法:

com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor.CompositeTransducedAccessorImpl.hasValue(BeanT) 

上述方法將呈現元素,如果值不爲空任何適配器運行之前。

是否有任何方法可以重寫JAXB中使用的訪問器,以便在確定是否呈現該元素之前運行該適配器?有另一種方法來實現我想要的嗎?

+0

看看JAXB綁定http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/1.5/tutorial/doc /JAXBUsing4.html – anazimok 2012-08-10 01:45:32

+0

是的,我看了一下。不幸的是,模式文檔相當複雜,我需要將其應用於大約30種不同的模式。據我所知,必須爲每個綁定定義一個模式。有沒有辦法將綁定應用於所有生成的JAXB類? – 2012-08-10 02:33:41

+1

您可以定義globalBinding請參閱我發佈的鏈接,我不記得哪個屬性確切,但其中一個屬性添加nillable到每個元素。也許這將有助於:http://stackoverflow.com/questions/4413281/how-do-i-prevent-jaxbelementstring-from-being-generated-in-a-cxf-web-service-c – anazimok 2012-08-10 03:40:36

回答

13

注:我是EclipseLink JAXB (MOXy)鉛和JAXB (JSR-222)專家小組的成員。

你所做的是對的,你看到的錯誤是由於我認爲是JAXB reference implementation中的一個錯誤。 JAXB RI應該能夠處理從XmlAdapter返回的空值。這個用例適用於EclipseLink JAXB(MOXy),我將在下面用一個例子來演示。

StringAdapter

下面是一個implmentation,它大約是什麼一個你生成的XML模式的Java模型後,你會得到(見http://blog.bdoughan.com/2011/08/xml-schema-to-java-generating.html)。

package forum11894193; 

import javax.xml.bind.annotation.adapters.XmlAdapter; 

public class StringAdapter extends XmlAdapter<String, String> { 

    @Override 
    public String marshal(String string) throws Exception { 
     if("".equals(string)) { 
      return null; 
     } 
     return string; 
    } 

    @Override 
    public String unmarshal(String string) throws Exception { 
     return string; 
    } 

} 

包信息

由於您註冊一個全球性的適配器,它將從一個package-info類類似下面引用(參見:http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html)。

@XmlJavaTypeAdapters({ 
    @XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class) 
}) 
package forum11894193; 

import javax.xml.bind.annotation.adapters.*; 

下面是一個示例域類有幾個String領域。由於XmlAdapter已在包級別註冊,因此它將應用於該包中的所有映射的字符串字段/屬性。

package forum11894193; 

import javax.xml.bind.annotation.*; 

@XmlRootElement 
@XmlAccessorType(XmlAccessType.FIELD) 
public class Root { 

    String a; 
    String b; 
    String c; 

} 

演示

在演示代碼下面我們將要創建的Root實例設置幾個字段以"",然後將其編組爲XML。

package forum11894193; 

import javax.xml.bind.*; 

public class Demo { 

    public static void main(String[] args) throws Exception { 
     JAXBContext jc = JAXBContext.newInstance(Root.class); 

     Root root = new Root(); 
     root.a = ""; 
     root.b = "b"; 
     root.c = ""; 

     Marshaller marshaller = jc.createMarshaller(); 
     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
     marshaller.marshal(root, System.out); 
    } 

} 

輸出使用JAXB RI

使用JAXB RI與在NPE這個例子的結果。堆棧跟蹤不同,但最有可能使用不同的編組方法。我還使用JDK中包含的JAXB RI版本,該版本被重新打包爲com.sun.xml.internal.bind.v2

Exception in thread "main" java.lang.NullPointerException 
    at com.sun.xml.internal.bind.v2.runtime.output.Encoded.setEscape(Encoded.java:96) 
    at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.doText(UTF8XmlOutput.java:294) 
    at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.java:283) 
    at com.sun.xml.internal.bind.v2.runtime.output.IndentingUTF8XmlOutput.text(IndentingUTF8XmlOutput.java:141) 
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:293) 
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:179) 
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:166) 
    at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:239) 
    at com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:87) 
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306) 
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561) 
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290) 
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462) 
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314) 
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243) 
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75) 
    at forum11894193.Demo.main(Demo.java:17) 

輸出使用的EclipseLink JAXB(莫西)

當莫西被用作JAXB提供你所需的輸出。有關將MOXy指定爲JAXB提供者的信息,請參閱:http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

<?xml version="1.0" encoding="UTF-8"?> 
<root> 
    <b>b</b> 
</root> 
+0

我看過你的文章幾次,但沒有意識到'bug'是相同的,因爲堆棧跟蹤是不同的。我同意這是JAXB實現中的一個錯誤。我會嘗試切換到MOXY。你會期望得到的XML是相同的嗎?如果不是,它可能不是一個選項。 – 2012-08-13 20:35:59

+0

@JBarclay - 是的結果XML應該是相同的。EclipseLink JAXB(MOXy)現在是WebLogic中的默認JAXB提供程序,可讓您瞭解您所期望的兼容性。 – 2012-08-13 20:39:28

+0

我已經切換到MOXY,它可以解決您所說的問題。不幸的是,MOXY對於無法使用的元素表現不佳。我已經更新了這個問題,有什麼想法? – 2012-08-13 21:47:14

0

空字符串仍然是一個值。這就是創建元素的原因。這樣說,如果字符串爲空,那麼在setter方法中將變量設置爲null?

此外,檢查此線程出 JAXB: how to make JAXB NOT to unmarshal empty string to 0

+0

我知道默認行爲是正確的,我想改變它。我無法更改setter方法或使用該鏈接中的解決方案,因爲JAXB類型會不斷重新生成。我需要由JAXB生成的類來包含解決方案。 – 2012-08-12 21:37:31

0

使用下面的代碼到你馬歇爾類

public static String toXml(Object o, Class clazz, boolean isFormatted, boolean isEmptyNodes) { 
     try { 
      Map<String, Object> properties = new HashMap<String, Object>(1); 
      if(isEmptyNodes) { 
       SessionEventListener sessionEventListener = new NullPolicySessionEventListener(); 
       properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener); 
      } else { 
       SessionEventListener sessionEventListener = new DiscardEmptyTagSessionEventListener(); 
       properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener); 
      } 

      // Create a JaxBContext 
      JAXBContext jc = JAXBContext.newInstance(new Class[] {clazz}, properties); 
      StringWriter sw = new StringWriter(); 

      // Create the UnMarshaller Object using the JaxB Context 
      Marshaller marshaller = jc.createMarshaller(); 

      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormatted); 
      // remove the xml version line from the output 
      marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE); 
      // Marshal the employee object to XML and print the output to console 
      marshaller.marshal(o, sw); 

      return sw.toString(); 
     } catch (JAXBException e) { 
      throw new RuntimeException(e.getMessage(), e); 
     } 
}