【问题标题】:JAXB element that is both optional and nillable可选且可空的 JAXB 元素
【发布时间】:2013-11-09 00:58:33
【问题描述】:

我已重新格式化问题,希望能更清楚地表达我的意图。

架构
我正在编写一些 Web 服务,我将使用 JAX-WS 自己发布这些服务。我们使用了一段时间的过程是首先编写一个只定义请求和响应对象的模式。这将发送给客户以批准 xml 消息的结构。我不想自己编写整个 wsdl,因为它比基本架构更复杂。

接下来,我使用 JAXB 命令 xjc 根据架构中的请求和响应类型生成类。然后,我将这些类用作参数并在 JAX-WS 带注释的端点类上返回类型。

这给了我一个可以调用的网络服务。它让我可以更好地控制正在发送和返回的 xml,而且还可以自动执行编写完整 wsdl 所需的重复操作。

问题
在架构中,我有一个这样的元素:

<xs:element name="myElement" type="xs:string" nillable="true" minOccurs="0" /> 

所以我想区分用户设置为空还是空白。生成的类则具有此属性。

@XmlElementRef(name = "myElement", namespace = "/mynamespace", type = JAXBElement.class)
protected JAXBElement<String> myElement;

这样做的效果是该元素既不是可空的也不是可选的。 JAX-WS 作为 wsdl 的一部分编写的模式已将该元素设置为强制且不可为空,如果我关闭模式验证,我仍然无法将 nil 传递给我的对象。

尝试过的事情
如果我将其更改为必需和​​可空的,那么我会得到这个生成的代码。

@XmlElement(required = true, nillable = true)
protected String myElement;

如果我将其更改为可选且不可为空,那么我会得到这个生成的代码。

protected String myElement

因此,如果您使用 JAXB,您似乎可以同时拥有其中的一个或两者。彻底失望!

我也尝试过手动将生成的类更改为如下所示。

@XmlElementRef(name = "myElement", namespace = "/mynamespace", type = JAXBElement.class, required=false)
protected JAXBElement<String> myElement;

现在这个元素是可选的,但我仍然不能将它设置为 nil。这样做会产生一个值为空字符串的 JAXBElement。仅当您关闭模式验证时才会这样做,因为生成的 JAX-WS wsdl/schema 不会将元素设置为 nillable,因此它不是一个有效的请求。

总结
我相信这是 JAXB 的一个错误。 @XmlElementRef 注释具有将其设置为不需要的属性,但没有将字段设置为可为空的属性。

@XmlElement 注释具有 required 和 nullable 属性,但这些属性只会导致 null 对象,因此无法区分未包含在 xml 中的元素或包含但为 null 的元素。这就是为什么您需要将 @XmlElementRef 与 JAXBElement 一起使用。

我认为这个错误包括两个问题。首先,xjc 命令应该生成 required=false 的元素。其次,@XmlElementRef 上应该有一个属性来设置元素是否可以为空,这也应该设置。

有人知道修复/解决方法吗?我尝试用谷歌搜索,但只发现有人问同样的问题而没有答案。这通常意味着不可能……TIA。

附加
我使用的是 jaxb 2.2.6,maven 插件是 jaxb2-maven-plugin 1.5。

【问题讨论】:

    标签: java xml jaxb jax-ws


    【解决方案1】:

    TL;DR

    对于

    @XmlElementRef(name="foo", required=false)
    protected JAXBElement<String> foo;
    

    文档中不存在的节点将对应于该字段为空。文档中存在的带有 xsi:nil="true" 的 XML 元素将对应的值是 JAXBElement 的实例,其值为 null

    您还可以提供一个 XML 模式,而不是让 JAXB 使用包级别 @XmlSchema 注释上的 location 属性生成一个。

    @XmlSchema(
        ...
        location="http://www.example.com/schema/root.xsd")
    package forum19665550;
    
    import javax.xml.bind.annotation.XmlSchema;
    

    元帅/解组

    Java 模型

    这是一个具有两个字段的对象,可以表示可选数据和可空数据。

    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.annotation.*;
    
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Root {
    
        @XmlElementRef(name="foo", required=false)
        protected JAXBElement<String> foo;
    
        @XmlElementRef(name="bar", required=false)
        protected JAXBElement<String> bar;
    
    }
    

    对象工厂

    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.annotation.*;
    import javax.xml.namespace.QName;
    
    @XmlRegistry
    public class ObjectFactory {
    
        @XmlElementDecl(name="foo")
        public JAXBElement<String> createFoo(String foo) {
            return new JAXBElement<String>(new QName("foo"), String.class, foo);
        }
    
        @XmlElementDecl(name="bar")
        public JAXBElement<String> createBar(String bar) {
            return new JAXBElement<String>(new QName("bar"), String.class, bar);
        }
    
    }
    

    演示代码

    演示

    下面的演示代码将研究foobar 值的差异。您可以使用JAXBIntrospector 类来获取JAXBElement 实例的实际值。 EclipseLink JAXB (MOXy) 中存在一个与解组封装空值的 JAXBElement 实例相关的错误(请参阅:http://bugs.eclipse.org/420746)。

    import java.io.File;
    import javax.xml.bind.*;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            JAXBContext jc = JAXBContext.newInstance(Root.class, ObjectFactory.class);
    
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            File xml = new File("src/forum19665550/input.xml");
            Root root = (Root) unmarshaller.unmarshal(xml);
    
            System.out.println("foo was set:          " + (root.foo != null));
            System.out.println("bar was set:          " + (root.bar != null));
            System.out.println("foo value:            " + root.foo);
            System.out.println("bar value:            " + root.bar);
            System.out.println("foo unwrapped value:  " + JAXBIntrospector.getValue(root.foo));
            System.out.println("bar unwrapped value:  " + JAXBIntrospector.getValue(root.bar));
    
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(root, System.out);
        }
    
    }
    

    input.xml/Output

    在结果输出中,我们看到我们可以区分文档中不存在的元素和具有 `xsi:nil="true" 的元素,并且结果值仍然为 null。

    foo was set:          false
    bar was set:          true
    foo value:            null
    bar value:            javax.xml.bind.JAXBElement@4af42ea0
    foo unwrapped value:  null
    bar unwrapped value:  null
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
        <bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
    

    生成 XML 架构

    演示代码

    生成架构

    下面是一些 JAXB 代码,它们将从带注释的模型生成 XML Schema。

    import java.io.IOException;
    import javax.xml.bind.*;
    import javax.xml.transform.Result;
    import javax.xml.transform.stream.StreamResult;
    
    public class GenerateSchema {
    
        public static void main(String[] args) throws Exception {
            JAXBContext jc = JAXBContext.newInstance(Root.class);
    
            jc.generateSchema(new SchemaOutputResolver() {
    
                @Override
                public Result createOutput(String namespaceUri,
                        String suggestedFileName) throws IOException {
                    StreamResult result = new StreamResult(System.out);
                    result.setSystemId(suggestedFileName);
                    return result;
                }
    
            });
        }
    
    }
    

    输出

    这是生成的 XML 模式。你是对的,它并不表示 foobar 元素是可空的。

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    
      <xs:element name="bar" type="xs:string"/>
    
      <xs:element name="foo" type="xs:string"/>
    
      <xs:element name="root" type="root"/>
    
      <xs:complexType name="root">
        <xs:sequence>
          <xs:element ref="foo" minOccurs="0"/>
          <xs:element ref="bar" minOccurs="0"/>
        </xs:sequence>
      </xs:complexType>
    </xs:schema>
    

    提供 XML 架构

    您可以指向现有的包含更多信息的模型,而不是让 JAXB 从您的模型派生 XML Schema。

    包裹信息

    这是通过在包级别 @XmlSchema 注释上指定 location 属性来完成的。

    @XmlSchema(
        ...
        location="http://www.example.com/schema/root.xsd")
    package forum19665550;
    
    import javax.xml.bind.annotation.XmlSchema;
    

    【讨论】:

    • 我想你误会了。首先,这是一个请求,因此它将 xml 编组到一个对象中,而不是相反。其次,当我将此生成的类用作 Web 服务的一部分时,wsdl 将此元素显示为必需且不可空。我不明白为什么这在输出上有任何不同。可以将 JAXBElement 设置为 null,但响应不会针对 wsdl/schema 进行验证。
    • 谢谢,我稍微改写了一下,希望能更清楚。
    • @BenThurley - 我已经更新了我的答案,希望能更好地匹配您的问题。
    • 我不能轻易地将它指向一个模式。我正在使用 JAX-WS 将其部署为 Web 服务,因此 JAX-WS 使用 JAXB 注释来创建 wsdl。我可以手写 wsdl 并指向它,但我不想那样做。
    【解决方案2】:

    您可以通过以下方式自定义绑定

    <jaxb:globalBindings generateElementProperty="false" />
    

    Customized Binding 中所述,对于您所询问的完全相同的情况。

    我正在使用自定义绑定与 maven 插件 org.jvnet.jaxb2.maven2:maven-jaxb2-plugin

    【讨论】:

      【解决方案3】:

      据我了解,您 Ben 以下 XSD:

      <xs:element name="myElement" type="xs:string" nillable="true" minOccurs="0" /> 
      

      应该导致:

      @XmlElementRef(name = "myElement", namespace = "/mynamespace", type = JAXBElement.class, required = false)
      protected JAXBElement<String> myElement;
      

      对吗?

      但对于默认的 JAXB 实现,情况并非如此。看起来像 JAXB 中的错误。我在 JAXB issue tracker 中没有找到它。 required 属性在 2009 年左右在 JAXB 2.2 中被引入 @XmlElementRef,但显然没有人为这个问题制造问题。

      required 属性不能使用绑定自定义进行更改。

      在这种情况下,您可以:

      • 为 XJC 编写您自己的插件以将缺失的属性添加到 @XmlElementRef 注释。这并不难。更多信息here
      • 使用替代的 JAXB 实现(MOXy 工作正常 - required = false 是使用 MOXy JAXB compiler 生成的)
      • 等待修复 JAXB 的 Oracle 实现。

      无论您选择哪个选项,请在JAXB issue tracker 提出问题,以便问题得到解决。

      编辑:

      为了表明创建插件很容易,我创建了一个。您可以在我的github repository 中找到它。随意使用/复制/修改。我不保证它 100% 有效,但对于简单的情况,它就像一个魅力。

      EDIT2:

      如果基于 java 对象和 JAXB 注释生成的模式与您的接口不匹配,那么您可以使用 @WebService.wsdlLocation 指向您的原始、正确的 WSDL 和 XSD 文件。

      EDIT3:

      在您的情况下,JAXB 忽略了nil,这很奇怪。我使用 JAXB 2.2.6 和 2.2.7 进行了测试,nil 被正确识别:

      JAXBContext context = JAXBContext.newInstance(SomeElement.class);
      Unmarshaller unmarshaller = context.createUnmarshaller();
      String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><ns2:someElement xmlns:ns2=\"http://www.example.org/example/\"><myElement xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\"/></ns2:someElement>";
      SomeElement someElement = (SomeElement) unmarshaller
              .unmarshal(new StringReader(xml));
      assertThat(someElement.getMyElement().isNil(), is(true));
      

      能否检查一下你是否正确设置了 nil 属性,例如:

      <myElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
      

      如果正确,请尝试与您的班级一起运行测试。

      【讨论】:

      • 我已经尝试手动添加 required=false 属性。这使它成为可选的,但仍然不能为空。即使我关闭模式验证,它仍然没有将 JAXBElement 设置为 nill。即 getNil 返回 false。我可能不得不按照你的建议提出这个错误。
      • 对不起,但我不明白是什么将 JAXBElement 设置为 nill。您的意思是输入消息没有正确解组吗?顺便提一句。我更新了我的答案,以便您可以解决生成不正确 XML 架构的问题。
      • 是的,如果您调用将元素作为 nil 传递的 Web 服务,那么它不会反映在生成的 JAXBElement 中。如果您在 JAXBElement 上调用 getValue,您会得到一个空白字符串。如果你打电话给 isNil 你会得到假的。它也未能通过架构验证,所以我不得不将其关闭以达到这一目标。设置 wsdl 位置并不是一个真正的选项,因为我只有一个模式来定义请求和响应类型。我希望 JAX-WS 为我生成其余的 wsdl。据我所知,@XmlElementRef 不支持可选和 nillable。它只有一个属性可以根据需要设置。
      • 我无法重现 JAXB 解组 nil 不正确的问题。请看一下我的最新编辑。
      • 我使用您的示例生成了一些 xml,然后转换回对象。我不得不稍微摆弄它,但最终我能够对其进行编组和解组。但是,当涉及到模式验证时,我仍然无法解决问题,因为该元素在模式中不可为空。我不知道这是 JAX-WS 还是 JAXB 的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多