【问题标题】:With Spring WebClient, how do I deserialize different types for successful and failed responses?使用 Spring WebClient,我如何反序列化不同类型的成功和失败响应?
【发布时间】:2022-08-23 06:36:17
【问题描述】:

tbjorch\ 的回答使我走上了正确的道路,但我发现的一件事是我应该为HttpClientErrorExceptionHttpServerErrorException 使用静态.create 方法构造HTTP 异常。如果我不这样做,我的所有异常都将作为Exception 超类抛出,这是不可取的,因为它丢失了原始 Http 状态代码的上下文。

我是使用 Spring WebClient 的新手。我正在实现的 API 根据成功或失败使用不同的 JSON 结构进行响应。在失败的情况下,我希望能够反序列化响应正文并返回错误消息。根据下面的代码,我收到一条错误消息:

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83)

我同意,在 2 个地方屏蔽是没有意义的。但我不明白如何反序列化错误响应以获取错误消息。我在网上和 StackOverflow 上读到的所有内容都表明人们只是返回 Mono.error 或抛出异常,但在这些情况下他们似乎没有反序列化响应正文。这是我的代码:

  public boolean updatePassword(String id, String data) {
    final var responseSpec =
        client
            .post()
            .uri(builder -> builder.path(\"/{id}/change_password\").build(id))
            .header(HttpHeaders.AUTHORIZATION, \"Bearer \" + staticData.getAPIToken())
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromValue(data))
            .retrieve()
            .onStatus(
                HttpStatus::isError,
                error -> {
                  final var errorResponse = error.bodyToMono(ErrorResponse.class).block();
                  final var errorMsg = errorResponse.getCause().getSummary();

                  if (error.statusCode().is4xxClientError()) {
                    throw new HttpClientErrorException(error.statusCode(), errorMsg);
                  }
                  if (error.statusCode().is5xxServerError()) {
                    throw new HttpServerErrorException(error.statusCode(), errorMsg);
                  }
                  return Mono.error(
                      new HttpServerErrorException(
                          HttpStatus.INTERNAL_SERVER_ERROR, \"Something else.\"));
                })
            .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {});

    final var response = responseSpec.block();

    return response.containsKey(\"password\") && response.containsKey(\"provider\");
  }

PS。我也尝试过使用.exchangeToMono 而不是.retrieve,这样我就可以检查响应状态代码,然后为.bodyToMono 函数使用不同的类型,但是一旦我在成功之间编写了两种不同的类型和失败,我收到一个错误,指出无法推断类型参数。

    标签: java spring-boot spring-webclient


    【解决方案1】:

    无法完全尝试您的代码,但请尝试以下操作:

    public boolean updatePassword(String id, String data) {
        final var responseSpec =
            client
                .post()
                .uri(builder -> builder.path("/{id}/change_password").build(id))
                .header(HttpHeaders.AUTHORIZATION, "Bearer " /*+ staticData.getAPIToken()*/)
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(data))
                .retrieve()
                .onStatus(
                    HttpStatus::isError,
                    error -> error.bodyToMono(ErrorResponse.class)
                        .map(errorResponse -> {
                        final var errorMsg = errorResponse.getCause().getSummary();
                        if (error.statusCode().is4xxClientError()) {
                          throw new HttpClientErrorException(error.statusCode(), errorMsg);
                        }
                        if (error.statusCode().is5xxServerError()) {
                          throw new HttpServerErrorException(error.statusCode(), errorMsg);
                        }
                        return new HttpServerErrorException(
                                HttpStatus.INTERNAL_SERVER_ERROR, "Something else.");
                      }))
                .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {});
    
        final var response = responseSpec.block();
    
        return response.containsKey("password") && response.containsKey("provider");
      }
    

    【讨论】:

      【解决方案2】:

      我相信我已经找到了解决办法。

      如您所说,您确实想使用交换而不是检索。虽然,您可能缺少代码,这就是为什么它第一次对您不起作用的原因。 通过使用交换,您可以检查返回代码并根据状态代码写入不同的响应对象。

      在你的情况下,

      final var responseSpec =
              client
                  .post()
                  .uri(builder -> builder.path("/{id}/change_password").build(id))
                  .header(HttpHeaders.AUTHORIZATION, "Bearer " + staticData.getAPIToken())
                  .accept(MediaType.APPLICATION_JSON)
                  .contentType(MediaType.APPLICATION_JSON)
                  .body(BodyInserters.fromValue(data))
                  .exchange()
      
      if (respsonseSpec.statusCode().is2xxSuccessful()) { 
        Map<String, Object> data = 
             responseSpec.bodyToMono(new ParameterizedTypeReference<Map<String, Object>() 
             {}).block()
          
         /* handle successful response  */ 
      
      } else if (responseSpec.StatusCode().is4xxClientError()){
        ErrorResponse errorResponse = responseSepc.bodyToMono(ErrorResponse.class).block()
      
        /* handle error */ 
      }
      /*... other status code cases */ 
      

      希望这有助于根据状态代码反序列化为不同类型

      【讨论】:

        猜你喜欢
        • 2022-08-23
        • 1970-01-01
        • 2011-12-02
        • 1970-01-01
        • 2021-04-10
        • 2021-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多