PAYATHON 2026

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 方法签名。

备注:内容仅供参考。