繁体   English   中英

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

[英]Invalid wsdl generated by spring-ws when the request element doesn't end with 'Request'

我必须准备一个 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操作是OK的,看起来像这样:

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

我该如何解决? 如何使无Request元素正确显示为 wsdl 中的 soap 操作输入?

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

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

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

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

例如,您可以将其设置为Req而不是Request ,然后构建将自动生成一个新的GetCountryReq类,然后需要手动调整ApplicationTestsCountryEndpoint的代码,消除编译错误(因为它们仍然指向以前的现有的GetCountryRequest类),但还要确保更改CountryEndPoint类中@PayloadRoot注释的localPart = "getCountryReq"属性。

我试过了,构建很顺利,WSDL 也相应地更新了。

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

wsdl11Definition.setRequestSuffix("");

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

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

DefaultWsdl11Definition 通过后缀解决这个问题。 或者,更具体地说,它委托给 SuffixBasedMessagesProvider 和 SuffixBasedPortTypesProvider 来执行此操作。
因此,如果您有一些其他首选方法来确定什么构成输入/输出消息,您将必须编写自己的消息提供程序和/或端口类型提供程序。

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

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

但他敞开了大门:编写您自己的SuffixBasedMessagesProviderSuffixBasedPortTypesProvider 但是,他还在DefaultWsdl11Definition (实例化这些提供程序的地方)中将所有内容都保留为私有,因此您还需要编写(复制)自己的 WSDL11 定义映射器。

这是我当时遵循的过程:

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

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

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

但自从我踏上旅程后,这里是可行的解决方案:

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

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

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

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自定义类(本质上是默认类的副本,只是实例化上面的类,为简洁起见删除了导入):

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

}

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

@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去掉Request后缀,会生成新的GetCountry类,你需要手动移除之前存在的GetCountryRequest并修复上面提到的编译错误(测试和端点类,不要'不要忘记更新 @PayloadRoot 注释)。

然后构建将运行良好。 运行Application main,生成的 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="getCountry">
    <soap:body use="literal"/>
  </wsdl:input>
  <wsdl:output name="getCountryResponse">
    <soap:body use="literal"/>
  </wsdl:output>
</wsdl:operation>
</wsdl:binding>

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

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

您用作起点的教程使用注释而不是名称空间,但应该有一种方法来指定文档中提到的 requestSuffix 和 responseSuffix 属性。 但是,恐怕您不能将它们留空。

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

有更简单的方法。 代替 DefaultWsdl11Definition 使用 ProviderBasedWsdl4jDefinition 并设置所有默认提供者,但一个 - 代替 SuffixBasedPortTypesProvider 使用这样的东西:

public class DelphiStylePortTypesProvider extends AbstractPortTypesProvider {

    @Override
    protected String getOperationName(Message msg) {
        if (isInputMessage(msg)) {
            return msg.getQName().getLocalPart();
        }
        if (isOutputMessage(msg)) {
            return msg.getQName().getLocalPart().replace("Response", "");
        }
        return "";
    }

    @Override
    protected boolean isInputMessage(Message msg) {
        return !msg.getQName().getLocalPart().endsWith("Response");
    }

    @Override
    protected boolean isOutputMessage(Message msg) {
        return msg.getQName().getLocalPart().endsWith("Response");
    }

    @Override
    protected boolean isFaultMessage(Message msg) {
        return false;
    }

}

@EnableWs配置类中:

@Bean(WSDL_SCHEMA_NAME)
public ProviderBasedWsdl4jDefinition providerBasedWsdl4jDefinition(XsdSchema xsdSchema) {
    var wsdl4jDefinition = new ProviderBasedWsdl4jDefinition();
    wsdl4jDefinition.setTargetNamespace(WSDL_TARGET_NAMESPACE);

    var suffixBasedPortTypesProvider = new CustomPortTypesProvider();
    suffixBasedPortTypesProvider.setPortTypeName(WSDL_PORT_TYPE_NAME);
    wsdl4jDefinition.setPortTypesProvider(suffixBasedPortTypesProvider);

    var inliningXsdSchemaTypesProvider = new InliningXsdSchemaTypesProvider();
    inliningXsdSchemaTypesProvider.setSchema(xsdSchema);
    wsdl4jDefinition.setTypesProvider(inliningXsdSchemaTypesProvider);

    wsdl4jDefinition.setMessagesProvider(new CustomMessagesProvider());

    var soapProvider = new SoapProvider();
    soapProvider.setLocationUri(WSDL_SCHEMA_URI);
    soapProvider.setServiceName(WSDL_SCHEMA_NAME);
    wsdl4jDefinition.setBindingsProvider(soapProvider);
    wsdl4jDefinition.setServicesProvider(soapProvider);

    return wsdl4jDefinition;
}

private static class CustomMessagesProvider extends SuffixBasedMessagesProvider {
    private static final String[] REQUEST_ELEMENTS =
            new String[] {"requestOne", "requestTwo"};

    @Override
    protected boolean isMessageElement(Element element) {
        if (super.isMessageElement(element)) {
            return true;
        } else {
            boolean isElement = "element".equals(element.getLocalName()) &&
                    "http://www.w3.org/2001/XMLSchema".equals(element.getNamespaceURI());
            String elementName = getElementName(element);
            return isElement && Arrays.asList(REQUEST_ELEMENTS).contains(elementName);
        }
    }
}

private static class CustomPortTypesProvider extends SuffixBasedPortTypesProvider {
    @Override
    protected String getOperationName(Message message) {
        String messageName = message.getQName().getLocalPart();
        if (isInputMessage(message)) {
            return messageName;
        } else if (isOutputMessage(message)) {
            return messageName.substring(0, messageName.length() - getResponseSuffix().length());
        } else if (isFaultMessage(message)) {
            return messageName.substring(0, messageName.length() - getFaultSuffix().length());
        }
        return null;
    }

    @Override
    protected boolean isInputMessage(Message message) {
        String messageName = message.getQName().getLocalPart();
        return !messageName.endsWith(getResponseSuffix()) && !messageName.endsWith(getFaultSuffix());
    }
}

我想最好的解决方案是编写一个您需要的 WSDL 文件。 在我看来,使用除 Spring Convention 以外的请求名称和响应名称从 XSD 文件创建自定义 WSDL 是不可路由的。 所以最好创建 WSDL 并将其导入项目,然后从中生成您的类。

我有同样的错误。 我的设置如下:

使用 mavenjaxb2-maven-plugin从现有 XSD 文件生成类

<!-- SOAP -->
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <version>2.5.0</version>
    <executions>
        <execution>
            <id>xjc</id>
            <goals>
                <goal>xjc</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <!-- The package of your generated sources -->
        <packageName>com.company</packageName>
        <addGeneratedAnnotation>true</addGeneratedAnnotation>
        <quiet>true</quiet>
        <sources>
            <source>${project.basedir}/src/main/resources/file.xsd</source>
        </sources>
    </configuration>
</plugin>

处理程序方法:

@PayloadRoot(namespace = "URI", localPart = "LOCAL_PART")
@ResponsePayload
public JAXBElement<StatusResponse> retrieveNextStatesRequest(@RequestPayload Request request) {
    // Logic
    return new Response();
}

然而,jaxb2-maven-plugin不可能添加XmlRoot注释。 但是,它提供了一个 ObjectFactory 来生成包装在JAXBElement实例中的实例。

因此,当相应地更改方法时,SOAP 请求现在可以按预期工作:

@PayloadRoot(namespace = "URI", localPart = "LOCAL_PART")
@ResponsePayload
public Response retrieveNextStatesRequest(@RequestPayload Request request) {
    // Logic
    return new ObjectFactory.createResponse(new Response());
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM