当请求元素不以“Request”结尾时,spring-ws 生成的 wsdl 无效

2022-09-03 04:43:26

我必须准备一个Web服务来接受已经定义的wsdl结构。我按照此处找到的教程进行了操作,测试项目的源代码可在此处下载

对于像这样的 xsd:

<xs:element name="getCountryRequest">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

应用程序返回的请求的 Wsdl 操作正常,如下所示:

<wsdl:binding name="CountriesPortSoap11" type="tns:CountriesPort">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getCountry">
        <soap:operation soapAction=""/>
        <wsdl:input name="getCountryRequest">
            <soap:body use="literal"/>
        </wsdl:input>
        <wsdl:output name="getCountryResponse">
            <soap:body use="literal"/>
        </wsdl:output>
    </wsdl:operation>
</wsdl:binding>

但是当我将xsd更改为(元素名称中没有“请求”)时:

<xs:element name="getCountry">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

wsdl 无效,并且没有指定:<input>

<wsdl:binding name="CountriesPortSoap11" type="tns:CountriesPort">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getCountry">
        <soap:operation soapAction=""/>
        <wsdl:output name="getCountryResponse">
            <soap:body use="literal"/>
        </wsdl:output>
    </wsdl:operation>
</wsdl:binding>

我该如何解决这个问题?如何使 -less 元素在 wsdl 中正确显示为 soap 操作输入?Request


答案 1

根据官方的Spring WS文档,请求/响应后缀是用于自动确定请求/响应并因此生成预期WSDL的默认后缀。

默认 Wsdl11 定义,它从 XSD 模式构建 WSDL。此定义循环访问架构中的所有元素元素,并为所有元素创建一条消息。接下来,它为以定义的请求或响应后缀结尾的所有消息创建 WSDL 操作。默认请求后缀是请求;默认响应后缀是 Response,尽管可以通过分别设置 requestSendfix 和 responseSuffix 属性来更改这些后缀。

因此,您可以在上述示例代码中更改配置类,方法中的后缀,并添加以下方法调用:WebServiceConfigdefaultWsdl11Definition

wsdl11Definition.setRequestSuffix("your-new-prefix-here");

例如,您可以将其设置为 代替 ,然后构建将自动生成一个新类,代码,然后需要手动调整,删除编译错误(因为它们仍将指向先前存在的类),但也确保更改类中注释的属性。ReqRequestGetCountryReqApplicationTestsCountryEndpointGetCountryRequestlocalPart = "getCountryReq"@PayloadRootCountryEndPoint

我尝试了一下,构建工作进展顺利,WSDL也相应地进行了更新。

这是关于将默认后缀更改为另一个后缀。但是,将其更改为空后缀又如何呢?

wsdl11Definition.setRequestSuffix("");

例外:后缀不得为空。春天不支持它。根据这个线程

基本上,问题是这样的:
我们必须区分哪些模式元素是wsdl消息,哪些不是。
在所有 wsdl 消息中,我们必须弄清楚哪些是输入(请求)消息。
在所有 wsdl 消息中,我们必须弄清楚哪些是输出(响应)消息。

DefaultWsdl11Definition 通过后缀来计算出这一点。或者,更具体地说,它委托给后缀基于消息提供程序和后缀BasedPortTypes提供程序来执行此操作。
因此,如果您有其他一些首选方法来确定输入/输出消息的构成要素,则必须编写自己的消息提供程序和/或端口类型提供程序。

简单地说:Spring-WS没有通用的方法来确定请求和响应的组成,而不是使用后缀。

注意:此消息的发布者是该类的作者 Arjen Poutsma(根据 javadocs),该组件根据这些后缀约定处理自动映射。DefaultWsdl11Definition

但他留下了一扇敞开的门:写你自己的和.但是,他还将所有内容都保留为私有(这些提供程序是实例化的),因此您还需要编写(复制)自己的 WSDL11 定义映射器。SuffixBasedMessagesProviderSuffixBasedPortTypesProviderDefaultWsdl11Definition

以下是我当时遵循的过程:

  • 创建您自己的 CustomSappixBasedMessagesProvider,覆盖该方法并删除对空后缀的检查,在您需要处理新映射的方法中setRequestSuffixisMessageElement
  • 创建您自己的 CustomSendixBasedPortTypesProvider,覆盖该方法并删除对空后缀的检查,在 and 方法中处理新映射setRequestSuffixgetOperationNameisInputMessage
  • 创建您自己的 CustomWsdl11Definition 作为现有 DefaultWsdl11Definition 的副本,并实例化上面创建的您自己的提供程序
  • 更改类,方法,以使用您自己的 CustomWsdl11Definition,以便应用整个自定义。WebServiceConfigdefaultWsdl11Definition

但是,空后缀会带来一些挑战,因为它对于任何元素都很好(即,每个元素都有一个空后缀)。这就是为什么我提到,和处理:在不断增长的WSDL上,你可能很容易最终对映射进行编码(对于GetCountry请求,GetCountryResponse是响应)或传递属性/映射(如上面提到的线程中所建议的那样),但再次在配置类中再次重复大部分XSD,使其难以维护,故障排除, 共享。isMessageElementisInputMessagegetOperationNameWebServiceConfig

所以,我真的建议不要走这条路,要么坚持默认的后缀(如果可能的话)要么创建一个新的后缀,但要避免空的后缀(毕竟库不允许它们)。

但是自从我踏上了这段旅程,以下是工作解决方案:

MySendixBasedMessagesProvider 自定义类(为简洁起见,删除了导入):

package hello;

public class MySuffixBasedMessagesProvider extends SuffixBasedMessagesProvider {

    protected String requestSuffix = DEFAULT_REQUEST_SUFFIX;

    public String getRequestSuffix() {
        return this.requestSuffix;
    }

    public void setRequestSuffix(String requestSuffix) {
        this.requestSuffix = requestSuffix;
    }

    @Override
    protected boolean isMessageElement(Element element) {
        if (isMessageElement0(element)) {
            String elementName = getElementName(element);
            Assert.hasText(elementName, "Element has no name");
            return elementName.endsWith(getResponseSuffix())
                    || (getRequestSuffix().isEmpty() ? true : elementName.endsWith(getRequestSuffix()))
                    || elementName.endsWith(getFaultSuffix());
        }
        return false;
    }

    protected boolean isMessageElement0(Element element) {
        return "element".equals(element.getLocalName())
                && "http://www.w3.org/2001/XMLSchema".equals(element.getNamespaceURI());
    }
}

MySendixBasedPortTypesProvider 自定义类(为简洁起见,删除了导入):

package hello;

public class MySuffixBasedPortTypesProvider extends SuffixBasedPortTypesProvider {

    private String requestSuffix = DEFAULT_REQUEST_SUFFIX;

    public String getRequestSuffix() {
        return requestSuffix;
    }

    public void setRequestSuffix(String requestSuffix) {
        this.requestSuffix = requestSuffix;
    }

    @Override
    protected String getOperationName(Message message) {
        String messageName = getMessageName(message);
        String result = null;
        if (messageName != null) {
            if (messageName.endsWith(getResponseSuffix())) {
                result = messageName.substring(0, messageName.length() - getResponseSuffix().length());
            } else if (messageName.endsWith(getFaultSuffix())) {
                result = messageName.substring(0, messageName.length() - getFaultSuffix().length());
            } else if (messageName.endsWith(getRequestSuffix())) {
                result = messageName.substring(0, messageName.length() - getRequestSuffix().length());
            }
        }
        return result;
    }

    @Override
    protected boolean isInputMessage(Message message) {
        String messageName = getMessageName(message);

        return messageName != null && !messageName.endsWith(getResponseSuffix());
    }

    private String getMessageName(Message message) {
        return message.getQName().getLocalPart();
    }

}

MyWsdl11Definition 自定义类(本质上是 Default 的副本,只是实例化上面的类,为了简洁起见,删除了导入):

package hello;

public class MyWsdl11Definition implements Wsdl11Definition, InitializingBean {

    private final InliningXsdSchemaTypesProvider typesProvider = new InliningXsdSchemaTypesProvider();

    private final SuffixBasedMessagesProvider messagesProvider = new MySuffixBasedMessagesProvider();

    private final SuffixBasedPortTypesProvider portTypesProvider = new MySuffixBasedPortTypesProvider();

    private final SoapProvider soapProvider = new SoapProvider();

    private final ProviderBasedWsdl4jDefinition delegate = new ProviderBasedWsdl4jDefinition();

    private String serviceName;

    public MyWsdl11Definition() {
        delegate.setTypesProvider(typesProvider);
        delegate.setMessagesProvider(messagesProvider);
        delegate.setPortTypesProvider(portTypesProvider);
        delegate.setBindingsProvider(soapProvider);
        delegate.setServicesProvider(soapProvider);
    }

    public void setTargetNamespace(String targetNamespace) {
        delegate.setTargetNamespace(targetNamespace);
    }

    public void setSchema(XsdSchema schema) {
        typesProvider.setSchema(schema);
    }

    public void setSchemaCollection(XsdSchemaCollection schemaCollection) {
        typesProvider.setSchemaCollection(schemaCollection);
    }

    public void setPortTypeName(String portTypeName) {
        portTypesProvider.setPortTypeName(portTypeName);
    }

    public void setRequestSuffix(String requestSuffix) {
        portTypesProvider.setRequestSuffix(requestSuffix);
        messagesProvider.setRequestSuffix(requestSuffix);
    }

    public void setResponseSuffix(String responseSuffix) {
        portTypesProvider.setResponseSuffix(responseSuffix);
        messagesProvider.setResponseSuffix(responseSuffix);
    }

    public void setFaultSuffix(String faultSuffix) {
        portTypesProvider.setFaultSuffix(faultSuffix);
        messagesProvider.setFaultSuffix(faultSuffix);
    }

    public void setCreateSoap11Binding(boolean createSoap11Binding) {
        soapProvider.setCreateSoap11Binding(createSoap11Binding);
    }

    public void setCreateSoap12Binding(boolean createSoap12Binding) {
        soapProvider.setCreateSoap12Binding(createSoap12Binding);
    }

    public void setSoapActions(Properties soapActions) {
        soapProvider.setSoapActions(soapActions);
    }

    public void setTransportUri(String transportUri) {
        soapProvider.setTransportUri(transportUri);
    }

    public void setLocationUri(String locationUri) {
        soapProvider.setLocationUri(locationUri);
    }

    public void setServiceName(String serviceName) {
        soapProvider.setServiceName(serviceName);
        this.serviceName = serviceName;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (!StringUtils.hasText(delegate.getTargetNamespace()) && typesProvider.getSchemaCollection() != null &&
                typesProvider.getSchemaCollection().getXsdSchemas().length > 0) {
            XsdSchema schema = typesProvider.getSchemaCollection().getXsdSchemas()[0];
            setTargetNamespace(schema.getTargetNamespace());
        }
        if (!StringUtils.hasText(serviceName) && StringUtils.hasText(portTypesProvider.getPortTypeName())) {
            soapProvider.setServiceName(portTypesProvider.getPortTypeName() + "Service");
        }
        delegate.afterPropertiesSet();
    }

    @Override
    public Source getSource() {
        return delegate.getSource();
    }

}

此外,该类的方法将更改如下(以使用上面的自定义):defaultWsdl11DefinitionWebServiceConfig

@Bean(name = "countries")
public Wsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
    MyWsdl11Definition wsdl11Definition = new MyWsdl11Definition();
    wsdl11Definition.setPortTypeName("CountriesPort");
    wsdl11Definition.setLocationUri("/ws");
    wsdl11Definition.setRequestSuffix("");
    wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service");
    wsdl11Definition.setSchema(countriesSchema);
    return wsdl11Definition;
}

请注意,它有效地将后缀设置为空。wsdl11Definition.setRequestSuffix("");

在此自定义之后,您可以更改 XSD 以删除请求后缀,将生成新的 GetCountry 类,您需要手动删除以前存在的 GetCountryRequest 并修复上面提到的编译错误(测试和终结点类,只是不要忘记更新@PayloadRoot注释)。

然后,构建将正常运行。运行主数据库,生成的 WSDL 将按照请求包含:Application

<wsdl:binding name="CountriesPortSoap11" type="tns:CountriesPort">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getCountry">
  <soap:operation soapAction=""/>
  <wsdl:input name="getCountry">
    <soap:body use="literal"/>
  </wsdl:input>
  <wsdl:output name="getCountryResponse">
    <soap:body use="literal"/>
  </wsdl:output>
</wsdl:operation>
</wsdl:binding>

希望它有帮助。这是一个真实的例子,说明配置上的约定极大地提供了什么,而不是框架中一个不可预见的小的更改在编写代码和添加自定义方面意味着什么。


答案 2

Spring-WS 自动 wsdl 曝光功能基于 http://docs.spring.io/spring-ws/site/reference/html/server.html#server-automatic-wsdl-exposure 中描述的约定

您用作起点的教程是使用注释而不是命名空间,但是应该有一种方法可以指定文档中提到的 requestSendfix 和 responseSappentSuffix 属性。但是,我担心您无法将它们留空。

或者,您可以公开手动编写的 WSDL。我建议这样做,因为你从一开始就给出了wsdl。


推荐