Return 来自 Spring 集成的 @Gateway 方法的 ResponseEntity
Return ResponseEntity from Spring Integration's @Gateway method
我有 IntegrationFlow
我调用 HTTP 端点的地方:
@Bean
public IntegrationFlow getInformationFlow(RestTemplate restTemplate) {
return IntegrationFlows.from(GET_RESPONSE_ENTITY)
.handle(Http
.outboundGateway(url + "/application/{code}", restTemplate)
.httpMethod(GET)
.uriVariable("code", "payload")
.expectedResponseType(new ParameterizedTypeReference<ResponseEntity<Information>>() {
})
).get();
}
由于 Gateway
,调用 getInformation
时会执行此流程
@MessagingGateway
public interface MyGateway {
@Gateway(requestChannel = GET_RESPONSE_ENTITY)
ResponseEntity<Information> getInformation(String code);
}
上面的代码抛出以下异常:
Caused by: org.springframework.http.converter.HttpMessageConversionException:
Type definition error: [simple type, class org.springframework.http.ResponseEntity];
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `org.springframework.http.ResponseEntity`
(no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Jackson 尝试反序列化为 ResponseEntity
而不是 Information
class
我的目标是首先检查状态代码,然后有选择地检查 Information
字段
是否可以从 @Gateway
注释的方法中 return ResponseEntity?
对您问题的简短回答:这取决于 MyGateway
合同的实施。处理 return 确实不是网关(或任何接口 API)的责任。他们只定义契约。妥善执行这样的合约已经是你的目标了。
我的意思是 Spring 与其 EIP 组件的集成并不比常规 Java 程序设计和架构更进一步。这个合约是 IntegrationFlow
作为一个实现只是一个特例。因此,问题不在于合同,而在于实现细节,在您的情况下是 HTTP 调用。
所以,最好问这样的问题:
How to return a ResponseEntity<Information>
from the Http.outboundGateway()
with a Jackson message converter?
这就是为什么我在 Gitter 上向您询问这个 SO 线程以更好地了解正在发生的事情。您最初的问题具有误导性,实际上与 @MessagingGateway
无关。我什至确定堆栈跟踪中的一些线索表明问题发生在 RestTemplate
调用中,而不是在 @MessagingGateway
.
中
现在正在尝试帮助您解决您的显式问题。
AbstractHttpRequestExecutingMessageHandler
不return一个ResponseEntity
当有一个body
:
protected Object getReply(ResponseEntity<?> httpResponse) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Map<String, Object> headers = this.headerMapper.toHeaders(httpHeaders);
if (this.transferCookies) {
doConvertSetCookie(headers);
}
AbstractIntegrationMessageBuilder<?> replyBuilder;
MessageBuilderFactory messageBuilderFactory = getMessageBuilderFactory();
if (httpResponse.hasBody()) {
Object responseBody = httpResponse.getBody();
replyBuilder = (responseBody instanceof Message<?>)
? messageBuilderFactory.fromMessage((Message<?>) responseBody)
: messageBuilderFactory.withPayload(responseBody); // NOSONAR - hasBody()
}
else {
replyBuilder = messageBuilderFactory.withPayload(httpResponse);
}
replyBuilder.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE,
httpResponse.getStatusCode());
return replyBuilder.copyHeaders(headers);
}
仅当没有 body 时。在那种情况下,这意味着没有任何内容可以映射到 payload
以用于回复消息,因此我们使用整个 ResponseEntity
.
如您在此代码片段中所见,StatusCode
映射到 org.springframework.integration.http.HttpHeaders.STATUS_CODE
回复消息 header。
关于这个问题的文档中也有一些解释:https://docs.spring.io/spring-integration/docs/current/reference/html/http.html#using-cookies。
从这里开始,这意味着您的 expectedResponseType
只能作为 Information
类型。 RestTemplate
及其 HttpMessageConverts
确实不知道如何处理要映射的 ResponseEntity
类型。
因为它可能 return 只是有效载荷中的一个 Information
如果它出现在响应中,或者可能 return 整个 ResponseEntity
空 body,看来您必须在 returning ResponseEntity<Information>
作为对 @MessagingGateway
呼叫的回复之前添加一些路由和转换逻辑。或者您可以修改该网关合同并通过路由器或过滤器在集成流程中真正实施状态代码检查 - 您的 @MessagingGateway
消费者将摆脱状态代码检查和 headers 转换等 HTTP 内容.
虽然在 AbstractHttpRequestExecutingMessageHandler
上有一些选项总是 return 整个 ResponseEntity
作为有效载荷可能没有什么坏处。欢迎提出 GH 问题,我们会考虑尽快实施!
TL;DR: 这是 Spring Integration >=5.5.0 中的一项新功能(感谢 OP)。您需要:
- 获取合适的Spring集成版本
- 更改您的
MessagingGateway
,使其 return 成为 ResponseEntity
- 将您的集成流程配置为不提取正文并期望
ResponseEntity
- 新建一个支持
ResponseEntity
的HttpMessageConverter
OP 创建的 GitHub issue/feature 请求导致在 Spring 集成中实现了一个实际功能。现在可以接收 ResponseEntity
个对象。 Artem Bilan 解释的内容在功能实施之前是正确的,如果标志 extractResponseBody
设置为 true
(默认),则仍然正确。如果设置为false
,那么很容易得到ResponseEntity
(现在)。
你需要做什么
首先获取 Spring 的新版本 (>=5.5.0) 集成到您的 POM.xml
或 build.gradle
dependencies {
...
implementation "org.springframework.integration:spring-integration-http:5.5.2"
...
}
在我的例子中,我之前有一个 REST 调用导致 String
。现在我想要一个ResponseEntity<String>
。所以我之前定义的 MessagingGateway
...
旧
@MessagingGateway
public interface Client {
/**
* Makes call to (another) Server
*/
@Gateway(requestChannel = "requestChannel", replyChannel = "replyChannel")
String makeSomeCall(@Header("url") String url, @Payload SomePayload sp);
}
... 变为(仅 return 值更改):
新
@MessagingGateway
public interface Client {
/**
* Makes call to (another) server
*/
@Gateway(requestChannel = "requestChannel", replyChannel = "replyChannel")
ResponseEntity<String> makeSomeCall(@Header("url") String url, @Payload SomePayload sp);
}
现在,如果我们就这样保留它,那么它仍然无法正常工作。我们需要告诉 Spring,不应提取 ResponseBody。我们在集成流程中这样做:
@Bean
MessageChannel replyChannel() {
return MessageChannels.direct("replyChannel").get();
}
@Bean
MessageChannel clientChannel() {
return MessageChannels.direct().get();
}
IntegrationFlow clientFlow() {
final SpelExpressionParser parser = new SpelExpressionParser();
return IntegrationFlows.from(clientChannel())
.handle(Http.outboundGateway(parser.parseExpression("headers.url"), restTemplate)
.charset("UTF-8")
.extractResponseBody(false) // <-- this is new w/ default being true!
.expectedResponseType(ResponseEntity.class)) // <-- this was String.class
.channel(clientReplyChannel())
.get();
}
如果我们把它留在这里,它仍然无法工作。该程序将无法找到合适的 HttpMessageConverter
。你需要自己写一个。这完全取决于您 ResponseEntity
的内容。值得庆幸的是,您很可能不需要自己编写整个 HttpMessageConverter
。您可以简单地扩展“适当的”一个并告诉它接受 ResponseEntity
。我的新 MessageConverter 看起来像这样。
@Component
public class ResponseEntityHttpMessageConverter extends StringHttpMessageConverter {
public ResponseEntityHttpMessageConverter() {
}
@Override
public boolean supports(final Class<?> clazz) {
return ResponseEntity.class == clazz;
}
}
Spring 开机应该可以自己捡起来。我相信网上有很多关于此的 material 以防你遇到困难。
提示:只是不要错误地实施HttpMessageConverter<ResponseEntity>
。 Spring 将为 MessageConverter
本身解包 ResponseEntity。所以 转换器应该只用于内容 .
我有 IntegrationFlow
我调用 HTTP 端点的地方:
@Bean
public IntegrationFlow getInformationFlow(RestTemplate restTemplate) {
return IntegrationFlows.from(GET_RESPONSE_ENTITY)
.handle(Http
.outboundGateway(url + "/application/{code}", restTemplate)
.httpMethod(GET)
.uriVariable("code", "payload")
.expectedResponseType(new ParameterizedTypeReference<ResponseEntity<Information>>() {
})
).get();
}
由于 Gateway
getInformation
时会执行此流程
@MessagingGateway
public interface MyGateway {
@Gateway(requestChannel = GET_RESPONSE_ENTITY)
ResponseEntity<Information> getInformation(String code);
}
上面的代码抛出以下异常:
Caused by: org.springframework.http.converter.HttpMessageConversionException:
Type definition error: [simple type, class org.springframework.http.ResponseEntity];
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `org.springframework.http.ResponseEntity`
(no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Jackson 尝试反序列化为 ResponseEntity
而不是 Information
class
我的目标是首先检查状态代码,然后有选择地检查 Information
字段
是否可以从 @Gateway
注释的方法中 return ResponseEntity?
对您问题的简短回答:这取决于 MyGateway
合同的实施。处理 return 确实不是网关(或任何接口 API)的责任。他们只定义契约。妥善执行这样的合约已经是你的目标了。
我的意思是 Spring 与其 EIP 组件的集成并不比常规 Java 程序设计和架构更进一步。这个合约是 IntegrationFlow
作为一个实现只是一个特例。因此,问题不在于合同,而在于实现细节,在您的情况下是 HTTP 调用。
所以,最好问这样的问题:
How to return a
ResponseEntity<Information>
from theHttp.outboundGateway()
with a Jackson message converter?
这就是为什么我在 Gitter 上向您询问这个 SO 线程以更好地了解正在发生的事情。您最初的问题具有误导性,实际上与 @MessagingGateway
无关。我什至确定堆栈跟踪中的一些线索表明问题发生在 RestTemplate
调用中,而不是在 @MessagingGateway
.
现在正在尝试帮助您解决您的显式问题。
AbstractHttpRequestExecutingMessageHandler
不return一个ResponseEntity
当有一个body
:
protected Object getReply(ResponseEntity<?> httpResponse) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Map<String, Object> headers = this.headerMapper.toHeaders(httpHeaders);
if (this.transferCookies) {
doConvertSetCookie(headers);
}
AbstractIntegrationMessageBuilder<?> replyBuilder;
MessageBuilderFactory messageBuilderFactory = getMessageBuilderFactory();
if (httpResponse.hasBody()) {
Object responseBody = httpResponse.getBody();
replyBuilder = (responseBody instanceof Message<?>)
? messageBuilderFactory.fromMessage((Message<?>) responseBody)
: messageBuilderFactory.withPayload(responseBody); // NOSONAR - hasBody()
}
else {
replyBuilder = messageBuilderFactory.withPayload(httpResponse);
}
replyBuilder.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE,
httpResponse.getStatusCode());
return replyBuilder.copyHeaders(headers);
}
仅当没有 body 时。在那种情况下,这意味着没有任何内容可以映射到 payload
以用于回复消息,因此我们使用整个 ResponseEntity
.
如您在此代码片段中所见,StatusCode
映射到 org.springframework.integration.http.HttpHeaders.STATUS_CODE
回复消息 header。
关于这个问题的文档中也有一些解释:https://docs.spring.io/spring-integration/docs/current/reference/html/http.html#using-cookies。
从这里开始,这意味着您的 expectedResponseType
只能作为 Information
类型。 RestTemplate
及其 HttpMessageConverts
确实不知道如何处理要映射的 ResponseEntity
类型。
因为它可能 return 只是有效载荷中的一个 Information
如果它出现在响应中,或者可能 return 整个 ResponseEntity
空 body,看来您必须在 returning ResponseEntity<Information>
作为对 @MessagingGateway
呼叫的回复之前添加一些路由和转换逻辑。或者您可以修改该网关合同并通过路由器或过滤器在集成流程中真正实施状态代码检查 - 您的 @MessagingGateway
消费者将摆脱状态代码检查和 headers 转换等 HTTP 内容.
虽然在 AbstractHttpRequestExecutingMessageHandler
上有一些选项总是 return 整个 ResponseEntity
作为有效载荷可能没有什么坏处。欢迎提出 GH 问题,我们会考虑尽快实施!
TL;DR: 这是 Spring Integration >=5.5.0 中的一项新功能(感谢 OP)。您需要:
- 获取合适的Spring集成版本
- 更改您的
MessagingGateway
,使其 return 成为ResponseEntity
- 将您的集成流程配置为不提取正文并期望
ResponseEntity
- 新建一个支持
ResponseEntity
的
HttpMessageConverter
OP 创建的 GitHub issue/feature 请求导致在 Spring 集成中实现了一个实际功能。现在可以接收 ResponseEntity
个对象。 Artem Bilan 解释的内容在功能实施之前是正确的,如果标志 extractResponseBody
设置为 true
(默认),则仍然正确。如果设置为false
,那么很容易得到ResponseEntity
(现在)。
你需要做什么
首先获取 Spring 的新版本 (>=5.5.0) 集成到您的 POM.xml
或 build.gradle
dependencies {
...
implementation "org.springframework.integration:spring-integration-http:5.5.2"
...
}
在我的例子中,我之前有一个 REST 调用导致 String
。现在我想要一个ResponseEntity<String>
。所以我之前定义的 MessagingGateway
...
旧
@MessagingGateway
public interface Client {
/**
* Makes call to (another) Server
*/
@Gateway(requestChannel = "requestChannel", replyChannel = "replyChannel")
String makeSomeCall(@Header("url") String url, @Payload SomePayload sp);
}
... 变为(仅 return 值更改):
新
@MessagingGateway
public interface Client {
/**
* Makes call to (another) server
*/
@Gateway(requestChannel = "requestChannel", replyChannel = "replyChannel")
ResponseEntity<String> makeSomeCall(@Header("url") String url, @Payload SomePayload sp);
}
现在,如果我们就这样保留它,那么它仍然无法正常工作。我们需要告诉 Spring,不应提取 ResponseBody。我们在集成流程中这样做:
@Bean
MessageChannel replyChannel() {
return MessageChannels.direct("replyChannel").get();
}
@Bean
MessageChannel clientChannel() {
return MessageChannels.direct().get();
}
IntegrationFlow clientFlow() {
final SpelExpressionParser parser = new SpelExpressionParser();
return IntegrationFlows.from(clientChannel())
.handle(Http.outboundGateway(parser.parseExpression("headers.url"), restTemplate)
.charset("UTF-8")
.extractResponseBody(false) // <-- this is new w/ default being true!
.expectedResponseType(ResponseEntity.class)) // <-- this was String.class
.channel(clientReplyChannel())
.get();
}
如果我们把它留在这里,它仍然无法工作。该程序将无法找到合适的 HttpMessageConverter
。你需要自己写一个。这完全取决于您 ResponseEntity
的内容。值得庆幸的是,您很可能不需要自己编写整个 HttpMessageConverter
。您可以简单地扩展“适当的”一个并告诉它接受 ResponseEntity
。我的新 MessageConverter 看起来像这样。
@Component
public class ResponseEntityHttpMessageConverter extends StringHttpMessageConverter {
public ResponseEntityHttpMessageConverter() {
}
@Override
public boolean supports(final Class<?> clazz) {
return ResponseEntity.class == clazz;
}
}
Spring 开机应该可以自己捡起来。我相信网上有很多关于此的 material 以防你遇到困难。
提示:只是不要错误地实施HttpMessageConverter<ResponseEntity>
。 Spring 将为 MessageConverter
本身解包 ResponseEntity。所以 转换器应该只用于内容 .