Web Services Support
本章节介绍了 Spring Integration 对 Web 服务的支持,包括:
此依赖项为项目所需:
- Maven
- Gradle
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-ws</artifactId>
<version>7.0.2</version>
</dependency>
compile "org.springframework.integration:spring-integration-ws:7.0.2"
出站 Web 服务网关
当您向通道发送消息以调用 Web 服务时,有两种选择,这两种选择都基于 Spring Web Services 项目:SimpleWebServiceOutboundGateway 和 MarshallingWebServiceOutboundGateway。前者接受 String 或 javax.xml.transform.Source 作为消息负载。后者支持 Marshaller 和 Unmarshaller 接口的任何实现。两者都需要 Spring Web Services 的 DestinationProvider 来确定要调用的 Web 服务的 URI。以下示例展示了调用 Web 服务的两种选项:
simpleGateway = new SimpleWebServiceOutboundGateway(destinationProvider);
marshallingGateway = new MarshallingWebServiceOutboundGateway(destinationProvider, marshaller);
在使用命名空间支持(稍后介绍)时,您只需设置一个 URI。在内部,解析器会配置一个固定的 URI DestinationProvider 实现。然而,如果您需要在运行时动态解析 URI,那么 DestinationProvider 可以提供诸如从注册表中查找 URI等行为。有关此策略的更多信息,请参阅 Spring Web Services DestinationProvider Javadoc。
从 5.0 版本开始,你可以为 SimpleWebServiceOutboundGateway 和 MarshallingWebServiceOutboundGateway 提供一个外部的 WebServiceTemplate 实例,你可以针对任何自定义属性进行配置,包括 checkConnectionForFault(它允许你的应用程序处理不符合规范的服务)。
入站 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 Services 的 MessageEndpoint 接口,因此可以按照标准 Spring Web Services 配置方式,通过 MessageDispatcherServlet 进行配置。
要将 SimpleWebServiceInboundGateway 和 MarshallingWebServiceInboundGateway 配置添加到 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 服务网关,请使用 ws 命名空间中的 outbound-gateway 元素,如下例所示:
<int-ws:outbound-gateway id="simpleGateway"
request-channel="inputChannel"
uri="https://example.org"/>
此示例未提供 'reply-channel'。如果 Web 服务返回非空响应,包含该响应的 Message 将被发送到请求消息 REPLY_CHANNEL 标头中定义的回复通道。如果该标头不存在,则会抛出通道解析异常。如果您希望将回复发送到另一个通道,请在 'outbound-gateway' 元素上提供 'reply-channel' 属性。
默认情况下,当你调用一个返回空响应的Web服务,并且在请求Message中使用了字符串负载时,不会发送回复Message。因此,你无需设置'reply-channel'或在请求Message中包含REPLY_CHANNEL头信息。如果你确实希望将空响应作为Message接收,可以将'ignore-empty-responses'属性设置为false。请注意,此设置仅对String对象有效,因为使用Source或Document对象会导致空响应,从而永远不会生成回复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 接口。当使用这样的 Marshaller 时,仅需配置 marshaller 属性即可。即使在使用 Marshaller 的情况下,您仍然可以为出站网关的 request-callback 提供引用。
对于任意一种出站网关类型,您都可以指定 destination-provider 属性来替代 uri(两者必须且仅能指定其一)。随后,您可以引用任何 Spring Web Services 的 DestinationProvider 实现(例如,在运行时从注册表中查找 URI)。
对于任意一种出站网关类型,message-factory 属性也可配置为引用任何 Spring Web Services 的 WebServiceMessageFactory 实现。
对于简单的入站网关类型,您可以将 extract-payload 属性设置为 false,从而将整个 WebServiceMessage 转发到请求通道,而不仅仅是其有效负载作为 Message。这样做可能很有用,例如,当自定义转换器直接处理 WebServiceMessage 时。
从 5.0 版本开始,web-service-template 引用属性允许您注入一个具有任何可能自定义属性的 WebServiceTemplate。
Web Service Java DSL 支持
Web Service Namespace Support 中展示的网关的等效配置如下所示:
@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 方案(参见 URIs and 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}&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/someApps?someKey={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 服务网关会自动映射 SOAP 操作头。默认情况下,它通过使用 DefaultSoapHeaderMapper 在 Spring Integration 的 MessageHeaders 之间进行复制。
您可以传入自定义的SOAP特定头部映射器实现,因为网关提供了支持此功能的属性。
除非通过 DefaultSoapHeaderMapper 的 requestHeaderNames 或 replyHeaderNames 属性明确指定,否则任何用户自定义的 SOAP 标头都不会复制到 SOAP 消息或从 SOAP 消息中复制。
在使用 XML 命名空间进行配置时,你可以通过 mapped-request-headers 和 mapped-reply-headers 属性来设置这些属性,也可以通过设置 header-mapper 属性来提供自定义的映射器。
在映射用户自定义头部时,值也可以包含简单的通配符模式(例如 myheader* 或 **myheader**)。例如,如果您需要复制所有用户自定义头部,可以使用通配符 *。
从版本4.1开始,AbstractHeaderMapper(DefaultSoapHeaderMapper 的超类)允许为 requestHeaderNames 和 replyHeaderNames 属性配置 NON_STANDARD_HEADERS 标记(除了现有的 STANDARD_REQUEST_HEADERS 和 STANDARD_REPLY_HEADERS),以映射所有用户定义的标头。
我们建议使用以下组合而非通配符 (*):STANDARD_REPLY_HEADERS, NON_STANDARD_HEADERS。这样做可以避免将 request 头部映射到回复中。
从 4.3 版本开始,你可以在头部映射模式前添加 ! 来否定该模式。否定模式具有优先级,因此像 STANDARD_REQUEST_HEADERS,thing1,thing*,!thing2,!thing3,thing5,!thing1 这样的列表不会映射 thing1、thing2 或 thing3。它会映射标准头部、thing4 和 thing5。(注意 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>
<cat>CAT</cat>
<can>CAN</can>
<dog>DOG</dog>
</soapenv:Header>
<soapenv:Body>
...
</soapenv:Body>
</soapenv:Envelope>
如果 mapped-request-headers 设置为 auth, ca*,则 auth、cat 和 can 标头会被映射,但 dog 不会被映射。
以下示例展示了如何从名为 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>test data</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));