Spring Integration outbound-gateway 如何发送 POST 参数
结论
该接口要求发送 JSON,不能使用表单或 multipart 数据。配置时需要注意:
- 将
Content-Type设为application/json。 - 请求 payload 保持为
Map或 Java DTO,再由MappingJackson2HttpMessageConverter序列化为 JSON。 expected-response-type指定的是响应体类型,不能用于获取 HTTP 状态码。- 如果要获取完整的
ResponseEntity,需要设置extract-response-body="false"。 - 用于提取状态码的 Transformer 应接在 outbound gateway 的响应通道上,不能接在请求通道上。
- 请求字段应与已经验证通过的 JSON 一致,使用
cardNumber、currencyCode和paymentAmount。
最终发送的请求体应为:
{
"cardNumber": "4444333322221111",
"currencyCode": "USD",
"paymentAmount": "12.00"
}
推荐配置
Gateway 接口可以继续返回 HttpStatus:
import java.util.Map;
import org.springframework.http.HttpStatus;
public interface MultipartRequestGateway {
HttpStatus postMultipartRequest(Map<String, Object> request);
}
请求数据的字段名需要与服务端契约完全一致:
Map<String, Object> request = new HashMap<String, Object>();
request.put("cardNumber", "4444333322221111");
request.put("currencyCode", "USD");
request.put("paymentAmount", "12.00");
MultipartRequestGateway gateway =
context.getBean("requestGateway", MultipartRequestGateway.class);
HttpStatus status = gateway.postMultipartRequest(request);
原测试代码使用了 cardNo 和 amount,SOAP-UI 请求使用的则是 cardNumber 和 paymentAmount。即使 JSON 已经成功发出,字段名不一致仍可能导致服务端校验失败。
XML 可以调整为:
<int:channel id="PaymentAuthRequestChannel"/>
<int:channel id="PaymentAuthReplyChannel"/>
<int:gateway id="requestGateway"
service-interface="org.springframework.integration.samples.http.MultipartRequestGateway"
default-request-channel="PaymentAuthRequestChannel">
<int:default-header name="Api-Key" value="XYZ"/>
<int:default-header name="Accept" value="application/json;v=3"/>
<int:default-header name="Content-Type" value="application/json"/>
</int:gateway>
<int-http:outbound-gateway
id="authOutboundGateway"
request-channel="PaymentAuthRequestChannel"
reply-channel="PaymentAuthReplyChannel"
request-factory="sslFactory"
header-mapper="headerMapper"
url="https://XX.XX.XX.XXX:11210/int-tsys-cc-payment-auth-web/integration/tsys/credit-cards/payments/xyz"
http-method="POST"
expected-response-type="java.lang.String"
extract-request-payload="true"
extract-response-body="false"
message-converters="jsonMessageConverter"/>
<int:transformer
input-channel="PaymentAuthReplyChannel"
expression="payload.statusCode"/>
<bean id="jsonMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
<bean id="sslFactory"
class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<constructor-arg ref="httpClient"/>
</bean>
<bean id="httpClient"
class="org.springframework.integration.samples.http.HttpClientFactory"/>
<bean id="headerMapper"
class="org.springframework.integration.http.support.DefaultHttpHeaderMapper">
<property name="inboundHeaderNames" value="*"/>
<property name="outboundHeaderNames"
value="Api-Key, Accept, Content-Type"/>
<property name="userDefinedHeaderPrefix" value=""/>
</bean>
这里没有为 Transformer 设置 output-channel。Transformer 会沿用原消息中的 replyChannel,把 HttpStatus 返回给调用 Gateway 的线程。也可以显式设置最终回复通道,但要同时调整 messaging gateway 的回复通道。
如果当前使用的 Spring Integration XML Schema 不支持 message-converters 属性,可以省略该属性,并确保 Jackson 2 已加入运行时 classpath。RestTemplate 检测到 Jackson 2 后,通常会注册 MappingJackson2HttpMessageConverter。所需的核心依赖是:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
是否会自动注册取决于项目使用的 Spring Framework 版本,以及 RestTemplate 的创建方式。日志中出现以下信息,说明 JSON converter 已经生效:
Writing [...] as "application/json" using
[org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@...]
为什么会出现这些异常
415 Unsupported Media Type
原配置声明了:
<int:default-header
name="Content-Type"
value="application/x-www-form-urlencoded"/>
AllEncompassingFormHttpMessageConverter 因此会把请求写成表单:
currencyCode=USD&paymentAmount=100.00&cardNumber=...
服务端要求的是 JSON,所以返回了 415 Unsupported Media Type。multipart 示例同样不适用于该接口,因为 SOAP-UI 验证通过的请求使用的是 JSON,并非 multipart 或表单提交。
no suitable HttpMessageConverter found
把 Content-Type 改成 application/json 只声明了数据格式。Spring 还需要相应的 converter,将 HashMap 序列化为 JSON。
Jackson 依赖缺失、MappingJackson2HttpMessageConverter 未注册,或者自定义 converter 列表覆盖了默认 converter,都可能引发以下错误:
no suitable HttpMessageConverter found for request type
[java.util.HashMap] and content type [application/json]
请求 payload 应保持为 Map,交给 Jackson 序列化。不要先把 Map 手动转换成普通字符串,再继续使用表单或 multipart 配置。
ResponseEntity 无法转换成 String
设置:
extract-response-body="false"
之后,outbound gateway 返回的 payload 类型是:
ResponseEntity<?>
如果 Gateway 方法声明的返回类型是 String,Spring Integration 会尝试把 ResponseEntity<?> 转换成 String,最终抛出:
No converter found capable of converting from type
org.springframework.http.ResponseEntity<?> to type java.lang.String
可以采用以下两种处理方式。
第一种是保留 ResponseEntity,让 Gateway 直接返回完整响应:
import java.util.Map;
import org.springframework.http.ResponseEntity;
public interface MultipartRequestGateway {
ResponseEntity<String> postMultipartRequest(Map<String, Object> request);
}
这种方式不需要状态码 Transformer:
<int-http:outbound-gateway
request-channel="PaymentAuthRequestChannel"
request-factory="sslFactory"
header-mapper="headerMapper"
url="https://XX.XX.XX.XXX:11210/int-tsys-cc-payment-auth-web/integration/tsys/credit-cards/payments/xyz"
http-method="POST"
expected-response-type="java.lang.String"
extract-response-body="false"
message-converters="jsonMessageConverter"/>
调用方可以这样读取状态码:
ResponseEntity<String> response =
gateway.postMultipartRequest(request);
HttpStatus status = response.getStatusCode();
第二种是让 Gateway 返回 HttpStatus,并在响应通道上配置 Transformer:
<int:transformer
input-channel="PaymentAuthReplyChannel"
expression="payload.statusCode"/>
Transformer 找不到 statusCode
原配置将 outbound gateway 和 Transformer 都接到了:
input-channel="PaymentAuthRequestChannel"
PaymentAuthRequestChannel 中的 payload 是请求 HashMap,没有 statusCode 属性,因此会出现:
Property or field 'statusCode' cannot be found on object
of type 'java.util.HashMap'
此外,当 DirectChannel 有多个订阅者时,每条消息只会交给其中一个订阅者。Transformer 与 outbound gateway 共用请求通道会造成竞争消费,甚至可能让请求没有发往 HTTP 服务。
通道应按以下顺序连接:
Gateway
-> PaymentAuthRequestChannel
-> HTTP outbound gateway
-> PaymentAuthReplyChannel
-> statusCode Transformer
-> 调用方
Transformer 处理的应该是 outbound gateway 返回的 ResponseEntity。
expected-response-type 的含义
以下配置有误:
expected-response-type="org.springframework.http.HttpStatus"
expected-response-type 指定的是响应体反序列化后的类型,与 HTTP 状态行的类型无关。这样设置会让 Spring 尝试把响应 body 解析为 HttpStatus。
如果服务端成功时返回空 body,可以使用:
expected-response-type="java.lang.String"
extract-response-body="false"
如果服务端返回 JSON,应将 expected-response-type 设置为对应的响应 DTO 类型。只要使用 extract-response-body="false",无论响应体是什么类型,外层 payload 都是 ResponseEntity<?>,可以通过 payload.statusCode 获取状态码。
还需要注意的事项
Accept 最好与已经验证成功的请求保持一致:
<int:default-header name="Accept" value="application/json;v=3"/>
Accept 表示期望接收的响应格式,Content-Type 表示实际发送的请求体格式,两者作用不同。
默认情况下,RestTemplate 会把 4xx 和 5xx 响应交给 ResponseErrorHandler,通常会直接抛出 HttpClientErrorException 或 HttpServerErrorException。上述方案可以正常返回 201 Created 等成功状态,但不能保证所有错误状态都会作为普通 HttpStatus 返回。如果业务需要统一接收 4xx 和 5xx 状态,应为所用的 HTTP 客户端配置自定义 ResponseErrorHandler。
示例中的 HTTPS 客户端信任所有证书,并关闭了主机名校验,只适合临时联调。生产环境应加载受信任的证书,同时保留主机名校验。
在较新的 Spring Framework 版本中,ResponseEntity#getStatusCode() 的返回类型可能是 HttpStatusCode。项目升级后如果遇到接口返回类型不匹配,应根据实际使用的 Spring 版本调整 Gateway 方法签名。
备注:内容仅供参考。
