java Sping Boot 3.2 REST API调用与通用响应:类型不匹配问题

cetgtptt  于 5个月前  发布在  Java
关注(0)|答案(1)|浏览(58)

我正在使用Sping Boot 应用程序(版本3.2),其中我使用RestClient类进行外部API调用。我的服务类中的updateProduct方法调用外部API,并使用invokeAPI方法返回通用响应。但是,我遇到了一个问题,APIResponseDTO结果类型没有被正确地推断为ProductDTO,而是被推断为LinkedHashMap
下面是相关代码:

public APIResponseDTO<ProductDTO> updateProduct(@NonNull ProductRequestDTO productRequest) {
        return invokeAPI(productRequest, HttpMethod.PUT, ProductDTO.class);
    }
    
    public <T, R> APIResponseDTO<T> invokeAPI(R requestDTO, HttpMethod httpMethod, Class<T> responseType) {
        Class<?> responseDTOClass = TypeFactory.defaultInstance().constructParametricType(APIResponseDTO.class, responseType).getRawClass();
        String productAPI = apiURL + apiVersion + PRODUCTS_API;
        HttpHeaders headers = new HttpHeaders();
        headers.set("client_id", apiClientId);
        headers.set("client_secret", apiClientSecret);
        Function<Boolean, String> accessTokenFunction = (forceRefresh) -> fetchAccessTokenResponse(forceRefresh).getAccessToken();
        return (APIResponseDTO<T>) restClient.exchange(productAPI,
                                                                          requestDTO,
                                                                          httpMethod,
                                                                          headers,
                                                                          responseDTOClass,
                                                                          accessTokenFunction,
                                                                          GatewayException.class);
    }

字符串
以下是相关的DTO:

@Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class APIResponseDTO<T> {
        @JsonProperty("status")
        private boolean status;

        @JsonProperty("errorCode")
        private int errorCode;

        @JsonProperty("errorMsg")
        private String errorMsg;

        @JsonProperty("result")
        private T result;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class ProductDTO {
        @JsonProperty("id")
        private String id;
    }


下面是Restclient

import java.net.URI;
    import java.util.Map;
    import java.util.function.Function;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.lang.NonNull;
    import org.springframework.retry.support.RetryTemplate;
    import org.springframework.web.client.RestClientException;
    import org.springframework.web.client.RestTemplate;

    @Slf4j
    public class RestClient extends RestTemplate {

        public static final String BEARER = "Bearer ";
        public static final boolean TOKEN_CACHE_ENABLED = true;
        public static final boolean TOKEN_CACHE_DISABLED = false;
        private final RetryTemplate retryTemplate;
        
        public <T> ResponseEntity<T> execute(
            String uri,
            HttpMethod method,
            HttpEntity<?> requestEntity,
            Class<T> responseType,
            Function<Boolean, String> accessTokenFunction) {
            
            ResponseEntity<T> responseEntity =
                    retryTemplate.execute(retryContext -> super.exchange(uri, method, requestEntity, responseType));
            
            if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.UNAUTHORIZED) {
                // If the response status is 401, retry with a new access token
                log.warn("Received 401 response from Salesforce. Retrying with a new access token.");
                requestEntity.getHeaders().set(AUTHORIZATION, BEARER + accessTokenFunction.apply(TOKEN_CACHE_DISABLED));
                responseEntity = exchange(uri, method, requestEntity, responseType);
            }
            return responseEntity;
        }

        public <T, R, E extends RuntimeException> Object exchange(
                String apiUrl,
                T requestDTO,
                HttpMethod httpMethod,
                HttpHeaders headers,
                Class<?> responseType,
                Function<Boolean, String> accessTokenFunction,
                Class<E> exceptionClass) {
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.set(AUTHORIZATION, BEARER + accessTokenFunction.apply(TOKEN_CACHE_ENABLED));
            HttpEntity<T> requestEntity = new HttpEntity<>(requestDTO, headers);
            ResponseEntity<?> responseEntity =
                    execute(apiUrl, httpMethod, requestEntity, responseType, accessTokenFunction);
            if (responseEntity != null
                    && responseEntity.getStatusCode().value() >= HttpStatus.OK.value()
                    && responseEntity.getStatusCode().value() <= HttpStatus.IM_USED.value()) {
                return responseEntity.getBody();
            } else {
                log.error("Failed to fetch the Response: {} for API {} | HttpMethod {} ", responseEntity, apiUrl, httpMethod);
                throw new CustomException(exceptionClass,"Failed to fetch the Response: " + responseEntity + " for API " + apiUrl + " | HttpMethod "+ httpMethod);
            }
        }
    }


似乎invokeAPI方法没有正确维护APIResponseDTOresult字段的泛型类型信息。它返回的不是预期的ProductDTO,而是LinkedHashMap
x1c 0d1x的数据
我怀疑这个问题可能与TypeFactory.defaultInstance()的使用有关,但我不知道如何解决它。任何帮助或建议,以解决此类型不匹配问题,将不胜感激。谢谢!
此外,我发现自己在invokeAPI方法中使用了类型转换(APIResponseDTO)。我正在寻找一种更干净的方法来处理这个问题,并避免显式的类型转换。具体来说,是否有一种方法可以修改exchange方法以返回泛型响应而不是Object?

b0zn9rqh

b0zn9rqh1#

ParameterizedTypeReference专门用于涉及集合或具有泛型的自定义对象的响应。这在运行时保留了关键的类型信息,确保了这些参数化类型的准确描述。
所以,你可以像这样测试请求:

var responseEntity = execute(apiUrl, httpMethod, new HttpEntity<>(requestDTO),
    new ParameterizedTypeReference<APIResponseDTO<ProductDTO>>() {
    });

字符串
这是我测试的调试结果(我需要使用自己的DTO与下游测试API保持一致:


的数据
对于更新的自定义RestClient bean代码,它看起来像这样:

@Component
public class MyRestClient extends RestTemplate {

  public <T> ResponseEntity<T> execute(
      String uri,
      HttpMethod method,
      HttpEntity<?> requestEntity,
      ParameterizedTypeReference<T> responseType) {
    return super.exchange(uri, method, requestEntity, responseType);
  }

  public <T, R> R exchange(
      String apiUrl,
      T requestDTO,
      HttpMethod httpMethod,
      ParameterizedTypeReference<R> responseType) {
    var responseEntity = execute(apiUrl, httpMethod, new HttpEntity<>(requestDTO), responseType);
    if (responseEntity != null
        && responseEntity.getStatusCode().value() >= HttpStatus.OK.value()
        && responseEntity.getStatusCode().value() <= HttpStatus.IM_USED.value()) {
      return responseEntity.getBody();
    } else {
      throw new RuntimeException();
    }
  }
}


您可以使用客户端进行请求,如下所示:(但是,您可以按照自己的方式调整)

private static <T> ParameterizedTypeReference<APIResponseDTO<T>> getParameterizedTypeReference(
      Class<T> clazz) {
    return new ParameterizedTypeReference<>() {
      @Override
      public Type getType() {
        return new ParameterizedType() {
          @Override
          public Type[] getActualTypeArguments() {
            return new Type[]{clazz};
          }

          @Override
          public Type getRawType() {
            return APIResponseDTO.class;
          }

          @Override
          public Type getOwnerType() {
            return null;
          }
        };
      }
    };
  }

  public <T, R> APIResponseDTO<T> invokeAPI(R requestDTO, HttpMethod httpMethod,
      Class<T> responseType) {
    String productAPI = apiURL + apiVersion + PRODUCTS_API;
    var responseDTOClass = getParameterizedTypeReference(responseType);
    return restClient.exchange(
        productAPI,
        requestDTO,
        httpMethod,
        responseDTOClass
    );
  }

相关问题