2012-08-10 62 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); 
     } 
}