# Web 服务支持

## Web 服务支持

本章描述 Spring 集成对 Web 服务的支持，包括：

* [出站 Web 服务网关](#webservices-outbound)

* [入站 Web 服务网关](#webservices-inbound)

* [Web 服务命名空间支持](#webservices-namespace)

* [出站 URI 配置](#outbound-uri)

* [WS 消息头](#ws-message-headers)

* [MTOM 支持](#mtom-support)

你需要在项目中包含此依赖项：

Maven

```
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-ws</artifactId>
    <version>5.5.9</version>
</dependency>
```

Gradle

```
compile "org.springframework.integration:spring-integration-ws:5.5.9"
```

### 出站 Web 服务网关

要在向通道发送消息时调用 Web 服务，你有两个选项，这两个选项都建立在[Spring Web Services](https://projects.spring.io/spring-ws/)项目上：`SimpleWebServiceOutboundGateway`和`MarshallingWebServiceOutboundGateway`。前者接受`String`或`javax.xml.transform.Source`作为消息负载。后者支持`Marshaller`和`Unmarshaller`接口的任何实现。两者都需要 Spring Web 服务`DestinationProvider`，以确定要调用的 Web 服务的 URI。下面的示例展示了调用 Web 服务的两个选项：

```
 simpleGateway = new SimpleWebServiceOutboundGateway(destinationProvider);

 marshallingGateway = new MarshallingWebServiceOutboundGateway(destinationProvider, marshaller);
```

|   |当使用名称空间支持（[稍后描述](#webservices-namespace)）时，你只需要设置一个 URI。<br/>在内部，解析器配置一个固定的 URI`DestinationProvider`实现。<br/>但是，如果你需要在运行时动态解析 URI，然后`DestinationProvider`可以提供这样的行为，例如从注册中心查找 URI。<br/>参见 Spring Web 服务[`DestinationProvider`]（https://DOCS. Spring.io/ Spring-ws/DOCS/current/api/org/springframework/ws/client/support/destinationprovider.html）Javadoc 以获取有关此策略的更多信息。|
|---| |

从版本 5.0 开始，你可以提供带有外部`SimpleWebServiceOutboundGateway`和`MarshallingWebServiceOutboundGateway`实例的`WebServiceTemplate`实例，你可以为任何自定义属性配置该实例，包括`checkConnectionForFault`（它允许你的应用程序处理不符合要求的服务）。

有关内部工作的更多详细信息，请参见 Spring Web 服务参考指南中涵盖[客户端访问](https://docs.spring.io/spring-ws/docs/current/reference/#client)的章节和涵盖[对象/XML 映射](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#oxm)的章节。

### 入站 Web 服务网关

要在接收到 Web 服务调用时将消息发送到通道，你还有两个选项：`SimpleWebServiceInboundGateway`和`MarshallingWebServiceInboundGateway`。前者从`WebServiceMessage`中提取一个`javax.xml.transform.Source`，并将其设置为消息有效负载。后者支持`Marshaller`和`Unmarshaller`接口的实现。如果传入的 Web 服务消息是 SOAP 消息，则将 SOAP 动作报头添加到转发到请求通道的`Message`的报头中。下面的示例展示了这两个选项：

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

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

这两个网关都实现了 Spring Web 服务`MessageEndpoint`接口，因此可以根据标准 Spring Web 服务配置使用`MessageDispatcherServlet`进行配置。

有关如何使用这些组件的更多详细信息，请参见 Spring Web 服务参考指南中涵盖[创建 Web 服务](https://docs.spring.io/spring-ws/docs/current/reference/#server)的章节。涵盖[对象/XML 映射](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#oxm)的章节也同样适用。

要将`SimpleWebServiceInboundGateway`和`MarshallingWebServiceInboundGateway`配置添加到 Spring WS 基础设施，你应该在`MessageDispatcherServlet`和目标`MessageEndpoint`实现之间添加`EndpointMapping`定义，就像在普通 Spring WS 应用程序中那样。为此（从 Spring 集成角度）， 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 定义。

有关更多信息，请参见[端点映射](https://docs.spring.io/spring-ws/docs/current/reference/#server-endpoint-mapping)。

### Web 服务命名空间支持

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

```
<int-ws:outbound-gateway id="simpleGateway"
                     request-channel="inputChannel"
                     uri="https://example.org"/>
```

|   |此示例不提供“回复通道”。<br/>如果 Web 服务返回一个非空响应，则包含该响应的`Message`将被发送到请求消息的`REPLY_CHANNEL`头中定义的回复通道。<br/>如果不可用，将抛出一个通道解析异常。<br/>如果你想将答复发送到另一个通道，请在“出站网关”元素上提供一个“reply-channel”属性。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |默认情况下，当你调用一个 Web 服务，在为请求`Message`使用字符串有效负载后返回一个空响应时，不会发送任何回复`Message`。因此，<br/>，你不需要在请求`Message`中设置“reply-channel”或有`REPLY_CHANNEL`头。<br/>如果你确实希望以`Message`的形式接收空响应，那么你可以将“ignore-empty-responses”属性设置为`false`。<br/>只对`String`对象有效，因为使用`Source`或`Document`对象会导致 null 响应，因此永远不会生成答复`Message`。|
|---| |

要设置入站 Web 服务网关，请使用`inbound-gateway`元素，如下例所示：

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

要使用 Spring OXM 编组器或解组器，你必须提供 Bean 引用。下面的示例展示了如何为出站编组网关提供 Bean 引用：

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

下面的示例展示了如何为入站编组网关提供 Bean 引用：

```
<int-ws:inbound-gateway id="marshallingGateway"
                    request-channel="requestChannel"
                    marshaller="someMarshaller"
                    unmarshaller="someUnmarshaller"/>
```

|   |大多数`Marshaller`实现还实现了`Unmarshaller`接口。<br/>当使用这样的`Marshaller`时，只有`marshaller`属性是必需的。<br/>即使在使用`Marshaller`时，也可以在出站网关上为`request-callback`提供参考。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

对于任何一种出站网关类型，你都可以指定`destination-provider`属性，而不是`uri`属性（正好其中之一是必需的）。然后，你可以引用任何 Spring Web 服务`DestinationProvider`实现（例如，在运行时从注册中心查找 URI）。

对于任何一种出站网关类型，`message-factory`属性也可以配置为引用任何 Spring Web 服务`WebServiceMessageFactory`实现。

对于简单的入站网关类型，你可以将`extract-payload`属性设置为`false`，以便将整个`WebServiceMessage`转发到请求通道，而不仅仅是将其有效负载作为`Message`转发到请求通道。这样做可能是有用的，例如，当一个自定义转换器直接针对`WebServiceMessage`工作时。

从版本 5.0 开始，`web-service-template`reference 属性允许你使用任何可能的自定义属性注入`WebServiceTemplate`。

### Web 服务 Java DSL 支持

以下片段显示了[Web 服务命名空间支持](#webservices-namespace)中所示网关的等效配置：

```
@Bean
IntegrationFlow inbound() {
    return IntegrationFlows.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 IntegrationFlows.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 服务支持的所有 URI 方案（参见[URI 和传输](https://docs.spring.io/spring-ws/docs/current/reference/#client-transports)），提供了`<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`]（https://DOCS. Spring.io/ Spring/DOCS/current/javadoc-api/org/springframework/web/util/uricomponentsbuilder.html））到 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 集成 Web 服务网关自动映射 SOAP 动作报头。默认情况下，通过使用[`DefaultSoapHeaderMapper`]（https://DOCS. Spring.io/ Spring-integration/api/org/springframework/integration/ws/defaultsoapheadermapper.html）来复制 Spring Integration`MessageHeaders`。

你可以传入你自己的特定于 SOAP 的头映射器的实现，因为网关具有支持这样做的属性。

除非`DefaultSoapHeaderMapper`的`requestHeaderNames`或`replyHeaderNames`属性明确指定，否则任何用户定义的 SOAP 头都不会复制到或复制到 SOAP 消息。

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

|   |在映射用户定义的标头时，值还可以包含简单的通配符模式（例如`myheader*`或`**myheader**`**）。<br/>例如，如果需要复制所有用户定义的标头，可以使用通配符：``**。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 4.1 开始，`AbstractHeaderMapper`（a`DefaultSoapHeaderMapper`超类）允许将`NON_STANDARD_HEADERS`令牌配置为`requestHeaderNames`和`replyHeaderNames`属性（除了现有的`STANDARD_REQUEST_HEADERS`和`STANDARD_REPLY_HEADERS`），以映射所有用户定义的标头。

|   |与使用通配符（`*`）不同，我们建议使用以下组合：`STANDARD_REPLY_HEADERS, NON_STANDARD_HEADERS`。<br/>这样做可以避免将`request`头映射到答复。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 4.3 开始，你可以通过在模式前面加上`!`来否定头映射中的模式。被否定的模式获得优先权，所以像`STANDARD_REQUEST_HEADERS,thing1,thing*,!thing2,!thing3,qux,!thing1`这样的列表不映射`thing1`，`thing2`，或`thing3`。它确实映射了标准的头，`thing4`和`qux`。（注意`thing1`包括在非否定形式和否定形式中。因为否定的值优先，所以`thing1`没有映射。）

|   |如果你有一个希望映射的以`!`开头的用户定义标头，则可以使用`\`对其进行转义，如下所示：`STANDARD_REQUEST_HEADERS,\!myBangHeader`。<br/>然后映射`!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-headers`是`auth, ca*`，则`auth`，`cat`和`can`标题被映射，但`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 信封：

```
<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 支持

编组入站和出站 Web 服务网关通过编组器的内置功能直接支持附件（例如，`Jaxb2Marshaller`提供`mtomEnabled`选项）。从版本 5.0 开始，简单的 Web 服务网关可以直接对入站和出站`MimeMessage`实例进行操作，这些实例有一个 API 来操作附件。当你需要发送带有附件的 Web 服务消息（来自服务器或客户端请求的回复）时，你应该直接使用`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));
```