1

我有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();
}

此流程在getInformation被调用时执行,这要归功于Gateway

@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)

杰克逊试图反序列化成ResponseEntity而不是Information

我的目标是首先检查状态代码,并可选择检查Information字段

是否可以从注释的方法返回 ResponseEntity @Gateway

4

2 回答 2

2

对您的问题的简短回答:这取决于该MyGateway合同的实施。处理返回实际上不是网关(或任何接口 API)的责任。他们只定义合同。正确执行这样的合同已经是您的目标。

我的意思是 Spring Integration 及其 EIP 组件并没有比常规的 Java 程序设计和架构更进一步。这只是一个特例,这个合约是IntegrationFlow一个实现。因此,问题不在于合同,而在于实施细节,在您的情况下这是一个 HTTP 调用。

所以,最好问这样的问题:

如何ResponseEntity<Information>Http.outboundGateway()带有杰克逊消息转换器的返回一个?

这就是为什么我在 Gitter 上问你这个 SO 线程以更好地了解发生了什么。您最初的问题具有误导性,并且与@MessagingGateway. 我什至确信堆栈跟踪中有一些线索表明问题发生在RestTemplate调用上,而不是在@MessagingGateway.

现在试图帮助您解决您的明确问题。

有a时AbstractHttpRequestExecutingMessageHandler不返回a :ResponseEntitybody

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);
}

只有在没有身体的情况下。在这种情况下,这意味着没有任何内容可以映射到payload回复消息中,因此我们使用整个ResponseEntity.

正如您在此代码片段中看到的,StatusCode被映射到org.springframework.integration.http.HttpHeaders.STATUS_CODE回复消息标头。

关于此事的文档中也有一些解释:https ://docs.spring.io/spring-integration/docs/current/reference/html/http.html#using-cookies 。

从这里开始,这意味着您expectedResponseType只能作为一种Information类型。RestTemplatewith HttpMessageConvertsit 确实不知道如何处理ResponseEntity要映射的类型。

由于它可能Information在响应中返回有效负载中的一个,或者可能返回ResponseEntity一个空主体的整体,看起来您必须添加一些路由和转换逻辑,然后再返回一个ResponseEntity<Information>作为对@MessagingGateway调用的回复。或者您可以修改该网关合同并通过路由器或过滤器在集成流中真正实施状态代码检查 - 您的@MessagingGateway消费者将摆脱 HTTP 的东西,如状态代码检查和标头转换。

尽管有一些选项AbstractHttpRequestExecutingMessageHandler总是将整个ResponseEntity作为有效负载返回可能没有什么坏处。随意提出一个 GH 问题,我们将考虑尽快实施它!

于 2021-03-26T14:01:16.703 回答
0

TL;DR:这是 Spring Integration >=5.5.0 中提供的一项新功能(感谢 OP)。你需要:

  1. 获取合适的 Spring Integration 版本
  2. 更改您的MessagingGateway,使其返回ResponseEntity
  3. 将您的集成流程配置为提取正文并期望ResponseEntity
  4. 新建一个HttpMessageConverter支持ResponseEntity

OP 创建的 GitHub 问题/功能请求导致在 Spring Integration 中实现了一个实际功能。现在可以接收ResponseEntity对象了。Artem Bilan解释的内容在实现该功能之前是正确的,如果标志设置为(默认)仍然是正确extractResponseBodytrue。如果它设置为false,那么很容易得到一个ResponseEntity(现在)。

你需要做什么

首先在您的POM.xmlbuild.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);
}

...变为(仅返回值更改):

新的

@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 Boot 应该能够自行拾取。我相信网上有很多关于这个的材料,以防你被卡住。

提示:只是不要犯错误来实施HttpMessageConverter<ResponseEntity>. Spring 将为MessageConverter自身解包 ResponseEntity。所以转换器应该只用于内容

于 2021-08-20T09:39:56.843 回答