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