我正在开发一个 Spring 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
方法没有正确维护 result
中 APIResponseDTO
字段的通用类型信息。它返回的是 ProductDTO
,而不是预期的 LinkedHashMap
。
我怀疑这个问题可能与使用
TypeFactory.defaultInstance()
有关,但我不确定如何解决它。任何有关解决此类型不匹配问题的帮助或建议将不胜感激。谢谢!
此外,我发现自己在 invokeAPI 方法中使用了类型转换 (APIResponseDTO)。我正在寻找一种更干净的方法来处理这个问题并避免显式类型转换。具体来说,有没有办法修改交换方法以返回通用响应而不是对象?
专门针对涉及带有泛型的集合或自定义对象的响应使用
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
);
}