Java JAXB 元素既可选又可空

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19665550/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 19:15:00  来源:igfitidea点击:

JAXB element that is both optional and nillable

javaxmljaxbjax-ws

提问by Ben Thurley

I have re-formatted the question to hopefully make my intentions clearer.

我已经重新格式化了这个问题,希望能让我的意图更清晰。

Architecture
I'm writing some web services that I will be publishing myself using JAX-WS. The process we have been using for some time is to first write a schema that only defines the request and response objects. This gets sent to the customer to approve the structure of the xml messages. I don't want to write the whole wsdl myself as it's more complicated than the basic schema.

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

Next I use the JAXB command xjc to generate classes based on the request and response types in my schema. I then use this classes as parameters and return types on a JAX-WS annotated endpoint class.

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

This now gives me a web service I can call. It gives me more control over the xml being sent and returned but also automates the repetition required in writing the full wsdl.

这现在为我提供了一个可以调用的 Web 服务。它使我可以更好地控制发送和返回的 xml,而且还自动执行编写完整 wsdl 所需的重复。

Problem
In the schema I have an element like this:

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

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

So I want to distinguish between the user setting null or blank. The generated class then has this attribute.

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

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

The effect of this is that the element becomes neither nillable or optional. The schema that JAX-WS writes as part of the wsdl has set the element to mandatory and not nillable and if I turn off schema validation I still can't pass nil through to my object.

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

Things tried
If I change it to be required and nillable then I get this generated code.

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

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

If I change it to optional and not nillable then I get this generated code.

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

protected String myElement

So you can have either or but not both it seems if you use JAXB. Thoroughly disappointing!

因此,如果您使用 JAXB,您可以选择其中之一,但不能同时拥有。彻底令人失望!

I've also tried manually changing the generated class to look like this.

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

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

This now makes the element optional but I still can't set it to nil. Doing so results in a JAXBElement with a value of a blank string. That's only if you turn schema validation off as the resulting JAX-WS wsdl/schema doesn't set the element as nillable, so its not a valid request.

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

Summary
It's my belief that this is a bug with JAXB. The @XmlElementRef annotation has an attribute to set it as not required but there is no attribute to set the field as nullable.

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

The @XmlElement annotation has attributes for both required and nullable but these just result in a null object so there would be no way to distinguish between an element not included in the xml or an element that was included but null. This is why you need to use @XmlElementRef along with JAXBElement.

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

I think the bug includes two issues. First the xjc command should generate the element with required=false. Second there should be an attribute on @XmlElementRef to set whether the element is nullable and this should be set too.

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

Does anyone know of a fix/workaround? I tried googling but only found people asking the same question without an answer. This usually means it's not possible... TIA.

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

Additional
I'm using jaxb 2.2.6 and the maven plugin is jaxb2-maven-plugin 1.5.

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

回答by bdoughan

TL;DR

TL; 博士

For

为了

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

An absent node in the document will correspond to this field being null. An XML element present in the document with xsi:nil="true"will correspond to the value being an instance of JAXBElementwith a value of null.

文档中不存在的节点将对应于该字段为空。存在于文档中的 XML 元素xsi:nil="true"将对应于值为 的实例的JAXBElementnull

You can also provide an XML schema instead of having JAXB generate one using the locationproperty on the package level @XmlSchemaannotation.

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

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

import javax.xml.bind.annotation.XmlSchema;


Marshal/Unmarshal

编组/解组

Java Model

Java模型

Root

This is an object with two fields that can represent optional and nillable data.

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

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;

}

ObjectFactory

对象工厂

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);
    }

}

Demo Code

演示代码

Demo

演示

The demo code below will investigate the differences in the values for fooand bar. You can use the JAXBIntrospectorclass to get the real value for an instance of JAXBElement. There is a bug in EclipseLink JAXB (MOXy) related to unmarshalling an instance of JAXBElementwrapping a null value (see: http://bugs.eclipse.org/420746).

下面的演示代码将调查foo和的值的差异bar。您可以使用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

输入.xml/输出

In the resulting output we see that we can differentiate between an element being absent from the document and an element with `xsi:nil="true" and still have the resulting value be null.

在结果输出中,我们看到我们可以区分文档中不存在的元素和具有 `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"/>

Generating an XML Schema

生成 XML 模式

Demo Code

演示代码

GenerateSchema

生成模式

Below is some JAXB code that will generate an XML Schema from the annotated model.

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

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;
            }

        });
    }

}

Output

输出

Here is the resulting XML Schema. You are correct that it doesn't indicate that the fooand barelements are nillable.

这是生成的 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>


Providing an XML Schema

提供 XML 模式

Instead of having JAXB derive an XML Schema from your model, you can point to your existing one that will contain more information.

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

package-info

包信息

This is done by specifying the locationproperty on the package level @XmlSchemaannotation.

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

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

import javax.xml.bind.annotation.XmlSchema;

回答by Dawid Pytel

As I understand you Ben the following XSD:

据我了解,Ben 以下 XSD:

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

Should result in:

应该导致:

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

Right?

对?

But for default JAXB implementation it is not the case. Looks like a bug in JAXB. I didn't find it in JAXB issue tracker. requiredattribute was introduced to @XmlElementRefin JAXB 2.2 in around 2009 but apparently no one created issue for this problem.

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

requiredattribute cannot be changed using Binding Customizations.

required无法使用绑定自定义更改属性。

In this situation you can:

在这种情况下,您可以:

  • write your own plugin for XJC to add missing attribute to @XmlElementRefannotation. It is not that difficult. More information here.
  • use alternative JAXB implementation (MOXy works fine - required = falseis generated using MOXy JAXB compiler)
  • wait for Oracle implementation of JAXB to be fixed.
  • 为 XJC 编写您自己的插件以将缺失的属性添加到@XmlElementRef注释中。这并不难。更多信息在这里
  • 使用替代 JAXB 实现(MOXy 工作正常 -required = false使用MOXy JAXB 编译器生成)
  • 等待 JAXB 的 Oracle 实现被修复。

No matter which option you choose please raise issue in JAXB issue trackerso that the problem will be fixed.

无论您选择哪个选项,请在JAXB 问题跟踪器中提出问题,以便解决问题。

EDIT:

编辑:

To show that creating plugin is easy I created one. You can find it in my github repository. Feel free to use/copy/modify at will. I don't give guarantee that it works in 100% but for simple cases works like a charm.

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

EDIT2:

编辑2:

If schema generated based on java objects and JAXB annotations does not match your interface then you can use @WebService.wsdlLocationto point to your original, correct WSDL and XSD files.

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

EDIT3:

编辑3:

It's weird that nilis ignored by JAXB in your case. I ran a test using JAXB 2.2.6 and 2.2.7 and nilis correctly recognized:

nil在您的情况下,JAXB 忽略了这很奇怪。我使用 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));

Could you check whether you correctly set nil attribute, e.g.:

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

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

If it is correct please try to run the test with your class.

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

回答by M-Zaiady

You can customise the binding by

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

<jaxb:globalBindings generateElementProperty="false" />

As documented in Customized Binding, for the same exact case you are asking about.

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

I'm using custom binding with maven plugin org.jvnet.jaxb2.maven2:maven-jaxb2-plugin

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