Web Services 支持

本章描述了 Spring Integration 对 Web Services 的支持,包括

您需要在项目中包含此依赖项

  • Maven

  • Gradle

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-ws</artifactId>
    <version>6.4.4</version>
</dependency>
compile "org.springframework.integration:spring-integration-ws:6.4.4"

出站 Web Service 网关

当您将消息发送到通道时,可以通过两种方式调用 Web Service,这两种方式都基于 Spring Web Services 项目:SimpleWebServiceOutboundGatewayMarshallingWebServiceOutboundGateway。前者接受 Stringjavax.xml.transform.Source 作为消息 Payload。后者支持 MarshallerUnmarshaller 接口的任何实现。两者都需要 Spring Web Services 的 DestinationProvider 来确定要调用的 Web Service 的 URI。以下示例展示了调用 Web Service 的这两种方式

 simpleGateway = new SimpleWebServiceOutboundGateway(destinationProvider);

 marshallingGateway = new MarshallingWebServiceOutboundGateway(destinationProvider, marshaller);
当使用命名空间支持(稍后描述)时,您只需设置一个 URI。在内部,解析器会配置一个固定的 URI DestinationProvider 实现。但是,如果您需要在运行时动态解析 URI,则 DestinationProvider 可以提供此类行为,例如从注册表中查找 URI。有关此策略的更多信息,请参阅 Spring Web Services DestinationProvider 的 Javadoc。

从 5.0 版本开始,您可以为 SimpleWebServiceOutboundGatewayMarshallingWebServiceOutboundGateway 提供外部的 WebServiceTemplate 实例,您可以为该实例配置任何自定义属性,包括 checkConnectionForFault(允许您的应用程序处理不规范的服务)。

有关内部工作的更多详细信息,请参阅 Spring Web Services 参考指南中关于客户端访问的章节以及关于对象/XML 映射的章节。

入站 Web Service 网关

当收到 Web Service 调用时将消息发送到通道,您同样有两种选择:SimpleWebServiceInboundGatewayMarshallingWebServiceInboundGateway。前者从 WebServiceMessage 中提取 javax.xml.transform.Source 并将其设置为消息 Payload。后者支持 MarshallerUnmarshaller 接口的实现。如果传入的 Web Service 消息是 SOAP 消息,则 SOAP action 头部会添加到转发到请求通道的 Message 的头部中。以下示例展示了这两种选项

 simpleGateway = new SimpleWebServiceInboundGateway();
 simpleGateway.setRequestChannel(forwardOntoThisChannel);
 simpleGateway.setReplyChannel(listenForResponseHere); //Optional

 marshallingGateway = new MarshallingWebServiceInboundGateway(marshaller);
 //set request and optionally reply channel

这两个网关都实现了 Spring Web Services 的 MessageEndpoint 接口,因此可以按照标准的 Spring Web Services 配置使用 MessageDispatcherServlet 进行配置。

有关如何使用这些组件的更多详细信息,请参阅 Spring Web Services 参考指南中关于创建 Web Service 的章节。关于对象/XML 映射的章节也同样适用。

要将 SimpleWebServiceInboundGatewayMarshallingWebServiceInboundGateway 的配置添加到 Spring WS 基础设施中,您应该在 MessageDispatcherServlet 和目标 MessageEndpoint 实现之间添加 EndpointMapping 定义,就像您在普通 Spring WS 应用程序中所做的那样。为此(从 Spring Integration 的角度来看),Spring WS 提供了以下便捷的 EndpointMapping 实现

  • o.s.ws.server.endpoint.mapping.UriEndpointMapping

  • o.s.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping

  • o.s.ws.soap.server.endpoint.mapping.SoapActionEndpointMapping

  • o.s.ws.server.endpoint.mapping.XPathPayloadEndpointMapping

您必须在应用上下文中指定这些类的 bean,并根据 WS 映射算法引用 SimpleWebServiceInboundGateway 和/或 MarshallingWebServiceInboundGateway 的 bean 定义。

有关更多信息,请参阅端点映射

Web Service 命名空间支持

要配置出站 Web Service 网关,请使用 ws 命名空间中的 outbound-gateway 元素,如下例所示

<int-ws:outbound-gateway id="simpleGateway"
                     request-channel="inputChannel"
                     uri="https://example.org"/>
此示例未提供 'reply-channel'。如果 Web Service 返回非空响应,包含该响应的 Message 将发送到请求消息的 REPLY_CHANNEL 头部中定义的回复通道。如果该通道不可用,则会抛出通道解析异常。如果您想将回复发送到另一个通道,请在 'outbound-gateway' 元素上提供 'reply-channel' 属性。
默认情况下,当您使用 String payload 发送请求 Message 并调用返回空响应的 Web Service 时,不会发送回复 Message。因此,您无需设置 'reply-channel' 或在请求 Message 中包含 REPLY_CHANNEL 头部。如果您确实希望接收空响应作为 Message,可以将 'ignore-empty-responses' 属性设置为 false。但这仅对 String 对象有效,因为使用 SourceDocument 对象会导致空响应,从而永远不会生成回复 Message

要设置入站 Web Service 网关,请使用 inbound-gateway 元素,如下例所示

<int-ws:inbound-gateway id="simpleGateway"
                    request-channel="inputChannel"/>

要使用 Spring OXM marshaller 或 unmarshaller,您必须提供 bean 引用。以下示例展示了如何为出站 marshalling 网关提供 bean 引用

<int-ws:outbound-gateway id="marshallingGateway"
                     request-channel="requestChannel"
                     uri="https://example.org"
                     marshaller="someMarshaller"
                     unmarshaller="someUnmarshaller"/>

以下示例展示了如何为入站 marshalling 网关提供 bean 引用

<int-ws:inbound-gateway id="marshallingGateway"
                    request-channel="requestChannel"
                    marshaller="someMarshaller"
                    unmarshaller="someUnmarshaller"/>
大多数 Marshaller 实现也实现了 Unmarshaller 接口。使用此类 Marshaller 时,只需 marshaller 属性。即使使用 Marshaller,您也可以为出站网关提供 request-callback 的引用。

对于任何一种出站网关类型,您都可以指定 destination-provider 属性而不是 uri(两者中必须指定一个)。然后您可以引用任何 Spring Web Services 的 DestinationProvider 实现(例如,在运行时从注册表中查找 URI)。

对于任何一种出站网关类型,还可以使用对任何 Spring Web Services 的 WebServiceMessageFactory 实现的引用来配置 message-factory 属性。

对于简单的入站网关类型,您可以将 extract-payload 属性设置为 false,以便将整个 WebServiceMessage 而不仅仅是其 Payload 作为 Message 转发到请求通道。例如,当自定义转换器直接处理 WebServiceMessage 时,这样做可能会很有用。

从 5.0 版本开始,web-service-template 引用属性允许您注入一个具有任何可能自定义属性的 WebServiceTemplate

Web Service Java DSL 支持

Web Service 命名空间支持 中展示的网关的等效配置如下所示

@Bean
IntegrationFlow inbound() {
    return IntegrationFlow.from(Ws.simpleInboundGateway()
                .id("simpleGateway"))
        ...
        .get();
}
@Bean
IntegrationFlow outboundMarshalled() {
    return f -> f.handle(Ws.marshallingOutboundGateway()
                    .id("marshallingGateway")
                    .marshaller(someMarshaller())
                    .unmarshaller(someUnmarshalller()))
        ...
}
@Bean
IntegrationFlow inboundMarshalled() {
    return IntegrationFlow.from(Ws.marshallingInboundGateway()
                .marshaller(someMarshaller())
                .unmarshaller(someUnmarshalller())
                .id("marshallingGateway"))
        ...
        .get();
}

其他属性可以在端点规格上以流畅的方式设置(属性取决于是否为出站网关提供了外部的 WebServiceTemplate)。示例

.from(Ws.simpleInboundGateway()
                .extractPayload(false))
.handle(Ws.simpleOutboundGateway(template)
            .uri(uri)
            .sourceExtractor(sourceExtractor)
            .encodingMode(DefaultUriBuilderFactory.EncodingMode.NONE)
            .headerMapper(headerMapper)
            .ignoreEmptyResponses(true)
            .requestCallback(requestCallback)
            .uriVariableExpressions(uriVariableExpressions)
            .extractPayload(false))
)
.handle(Ws.marshallingOutboundGateway()
            .destinationProvider(destinationProvider)
            .marshaller(marshaller)
            .unmarshaller(unmarshaller)
            .messageFactory(messageFactory)
            .encodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY)
            .faultMessageResolver(faultMessageResolver)
            .headerMapper(headerMapper)
            .ignoreEmptyResponses(true)
            .interceptors(interceptor)
            .messageSenders(messageSender)
            .requestCallback(requestCallback)
            .uriVariableExpressions(uriVariableExpressions))
.handle(Ws.marshallingOutboundGateway(template)
            .uri(uri)
            .encodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT)
            .headerMapper(headerMapper)
            .ignoreEmptyResponses(true)
            .requestCallback(requestCallback)
            .uriVariableExpressions(uriVariableExpressions))
)

出站 URI 配置

对于 Spring Web Services 支持的所有 URI 方案(请参阅 URI 和传输),都提供了 <uri-variable/> 替换。以下示例展示了如何定义它

<ws:outbound-gateway id="gateway" request-channel="input"
        uri="https://springsource.org/{thing1}-{thing2}">
    <ws:uri-variable name="thing1" expression="payload.substring(1,7)"/>
    <ws:uri-variable name="thing2" expression="headers.x"/>
</ws:outbound-gateway>

<ws:outbound-gateway request-channel="inputJms"
        uri="jms:{destination}?deliveryMode={deliveryMode}&amp;priority={priority}"
        message-sender="jmsMessageSender">
    <ws:uri-variable name="destination" expression="headers.jmsQueue"/>
    <ws:uri-variable name="deliveryMode" expression="headers.deliveryMode"/>
    <ws:uri-variable name="priority" expression="headers.jms_priority"/>
</ws:outbound-gateway>

如果您提供了 DestinationProvider,则不支持变量替换,并且如果您提供了变量,则会发生配置错误。

控制 URI 编码

默认情况下,在发送请求之前,URL 字符串会被编码(请参阅 UriComponentsBuilder)为 URI 对象。在某些非标准 URI 的场景中,进行编码是不希望的。<ws:outbound-gateway/> 元素提供了一个 encoding-mode 属性。要禁用 URL 编码,请将此属性设置为 NONE(默认情况下为 TEMPLATE_AND_VALUES)。如果您希望部分编码 URL 的某些部分,可以使用 <uri-variable/> 中的 expression 来实现,如下例所示

<ws:outbound-gateway url="https://somehost/%2f/fooApps?bar={param}" encoding-mode="NONE">
          <http:uri-variable name="param"
            expression="T(org.apache.commons.httpclient.util.URIUtil)
                                             .encodeWithinQuery('Hello World!')"/>
</ws:outbound-gateway>
如果您设置了 DestinationProvider,则 encoding-mode 将被忽略。

WS 消息头

Spring Integration Web Service 网关会自动映射 SOAP action 头部。默认情况下,它使用 DefaultSoapHeaderMapper 复制到 Spring Integration MessageHeaders 并从中复制。

您可以传入自己实现的 SOAP 特定头部映射器,因为网关具有支持此功能的属性。

除非在 DefaultSoapHeaderMapperrequestHeaderNamesreplyHeaderNames 属性中明确指定,否则任何用户定义的 SOAP 头部都不会被复制到 SOAP Message 或从 SOAP Message 中复制。

当您使用 XML 命名空间进行配置时,可以使用 mapped-request-headersmapped-reply-headers 属性来设置这些属性,您也可以通过设置 header-mapper 属性来提供自定义映射器。

映射用户定义的头部时,值也可以包含简单的通配符模式(例如 myheader**myheader*)。例如,如果您需要复制所有用户定义的头部,可以使用通配符字符:*

从 4.1 版本开始,AbstractHeaderMapperDefaultSoapHeaderMapper 的父类)允许为 requestHeaderNamesreplyHeaderNames 属性配置 NON_STANDARD_HEADERS 令牌(除了现有的 STANDARD_REQUEST_HEADERSSTANDARD_REPLY_HEADERS),以映射所有用户定义的头部。

建议使用以下组合代替通配符(*):STANDARD_REPLY_HEADERS, NON_STANDARD_HEADERS。这样做可以避免将 request 头部映射到 reply。

从 4.3 版本开始,您可以通过在模式前加上 ! 来否定头部映射中的模式。否定模式具有优先权,因此像 STANDARD_REQUEST_HEADERS,thing1,thing*,!thing2,!thing3,qux,!thing1 这样的列表不会映射 thing1thing2thing3。它会映射标准头部、thing4qux。(请注意,thing1 包含在非否定和否定形式中。由于否定值优先,因此 thing1 不会被映射。)

如果您有一个以 ! 开头的用户自定义头部,并且您确实希望映射它,可以使用 \ 进行转义,如下所示:STANDARD_REQUEST_HEADERS,\!myBangHeader。然后会映射 !myBangHeader

入站 SOAP 头部(入站网关的请求头部和出站网关的回复头部)被映射为 SoapHeaderElement 对象。您可以通过访问 Source 来探索其内容

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header>
        <auth>
            <username>user</username>
            <password>pass</password>
        </auth>
        <bar>BAR</bar>
        <baz>BAZ</baz>
        <qux>qux</qux>
    </soapenv:Header>
    <soapenv:Body>
        ...
    </soapenv:Body>
</soapenv:Envelope>

如果 mapped-request-headersauth, ca*,则 authcatcan 头部会被映射,但 qux 不会被映射。

以下示例展示了如何从名为 auth 的头部中获取名为 user 的值

...
SoapHeaderElement header = (SoapHeaderElement) headers.get("auth");
DOMSource source = (DOMSource) header.getSource();
NodeList nodeList = source.getNode().getChildNodes();
assertEquals("username", nodeList.item(0).getNodeName());
assertEquals("user", nodeList.item(0).getFirstChild().getNodeValue());
...

从 5.0 版本开始,DefaultSoapHeaderMapper 支持类型为 javax.xml.transform.Source 的用户定义头部,并将其作为 <soapenv:Header> 的子节点进行填充。以下示例展示了如何执行此操作

Map<String, Object> headers = new HashMap<>();

String authXml =
     "<auth xmlns='http://test.auth.org'>"
           + "<username>user</username>"
           + "<password>pass</password>"
           + "</auth>";
headers.put("auth", new StringSource(authXml));
...
DefaultSoapHeaderMapper mapper = new DefaultSoapHeaderMapper();
mapper.setRequestHeaderNames("auth");

前述示例的结果是以下 SOAP envelope

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header>
        <auth xmlns="http://test.auth.org">
            <username>user</username>
            <password>pass</password>
        </auth>
    </soapenv:Header>
    <soapenv:Body>
        ...
    </soapenv:Body>
</soapenv:Envelope>

MTOM 支持

marshalling 入站和出站 Web Service 网关通过 marshaller 的内置功能(例如,Jaxb2Marshaller 提供了 mtomEnabled 选项)直接支持附件。从 5.0 版本开始,简单 Web Service 网关可以直接处理入站和出站的 MimeMessage 实例,这些实例具有用于操作附件的 API。当您需要发送带有附件的 Web Service 消息(无论是来自服务器的回复还是客户端请求)时,您应该直接使用 WebServiceMessageFactory 并将带有附件的 WebServiceMessage 作为 payload 发送到网关的请求或回复通道。以下示例展示了如何执行此操作

WebServiceMessageFactory messageFactory = new SaajSoapMessageFactory(MessageFactory.newInstance());
MimeMessage webServiceMessage = (MimeMessage) messageFactory.createWebServiceMessage();

String request = "<test>foo</test>";

TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new StringSource(request), webServiceMessage.getPayloadResult());

webServiceMessage.addAttachment("myAttachment", new ByteArrayResource("my_data".getBytes()), "plain/text");

this.webServiceChannel.send(new GenericMessage<>(webServiceMessage));