使用 RestTemplate
,我查询远程 API 以返回预期类型(如果 HTTP 2xx)或 APIError(如果 HTTP 4xx/5xx)的对象。
因为响应对象是不确定的,所以我实现了一个自定义的 ResponseErrorHandler
并覆盖了 handleError(ClientHttpResponse clientHttpResponse)
以便在 APIError 发生时提取它。到目前为止一切顺利:
@Component
public class RemoteAPI {
public UserOrders getUserOrders(User user) {
addAuthorizationHeader(httpHeaders, user.getAccessToken());
HttpEntity<TokenRequest> request = new HttpEntity<>(HEADERS);
return restTemplate.postForObject(CUSTOMER_ORDERS_URI, request, UserOrders.class);
}
private class APIResponseErrorHandler implements ResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response) {
try {
APIError apiError = new ObjectMapper().readValue(response.getBody(), APIError.class);
} catch ...
}
}
private void refreshAccessToken(User user) {
addAuthorizationHeader(httpHeaders, user.getAccessSecret());
HttpEntity<TokenRequest> request = new HttpEntity<>(HEADERS);
user.setAccessToken(restTemplate.postForObject(TOKEN_REFRESH_URI, request, AccessToken.class));
}
}
挑战在于 getUserOrders()
或类似的 API 调用偶尔会因“可恢复”错误而失败;例如,API 访问 token 可能已过期。然后,我们应该在重新尝试 getUserOrders()
之前对 refreshAccessToken()
进行 API 调用。诸如此类的可恢复错误应该对用户隐藏,直到相同的错误多次发生,此时它们被视为不可恢复/严重。
任何“关键”错误(例如:第二次失败、完全身份验证失败或传输层失败)都应报告给用户,因为没有可用的自动恢复。
管理错误处理逻辑的最优雅和最稳健的方法是什么,要记住返回的对象类型直到运行时才知道?
选项 1:在每个 API 调用方法中使用 try/catch 将错误对象作为类变量:
@Component
public class RemoteAPI {
private APIError apiError;
private class APIResponseErrorHandler implements ResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response) {
try {
this.apiError = new ObjectMapper().readValue(response.getBody(), APIError.class);
} catch ...
}
}
public UserOrders getUserOrders(User user) {
try {
userOrders = restTemplate.postForObject(CUSTOMER_ORDERS_URI, request, UserOrders.class);
} catch (RestClientException ex) {
// Check this.apiError for type of error
// Check how many times this API call has been attempted; compare against maximum
// Try again, or report back as a failure
}
return userOrders;
}
}
优点:清楚最初调用的方法
缺点:将类变量用于临时值。调用 API 的每个方法都有大量样板代码。错误处理逻辑分布在多个方法中。
选项 2:用户对象作为类变量/ResponseErrorHandler 中的错误管理逻辑
@Component
public class RemoteAPI {
private User user;
private class APIResponseErrorHandler implements ResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response) {
try {
APIError apiError = new ObjectMapper().readValue(response.getBody(), APIError.class);
// Check this.apiError for type of error
// Check how many times this API call has been attempted; compare against maximum
// Try again...
getUserOrders();
...or report back as a failure
} catch ...
}
}
优点:错误管理逻辑位于一处。
缺点:用户对象现在必须是一个类变量并得到妥善处理,因为用户对象无法在 ResponseErrorHandler 中访问,因此无法将其传递给 getUserOrders(User)
和以前一样。需要跟踪每个方法被调用了多少次。
选项 3:RemoteAPI 类之外的错误管理逻辑
优点:将错误处理与业务逻辑分开
缺点:API 逻辑现在在另一个类中
感谢您的任何建议。
最佳答案
回答我自己的问题:原来问题本身就存在谬误。
我正在实现一个 ResponseErrorHandler
,因为我认为我需要它来解析响应,即使该响应返回时带有 HTTP 错误代码。事实上,情况并非如此。
This answer演示了可以通过捕获 HttpStatusCodeException
或使用标准 RestTemplate
将响应解析为对象。这消除了对自定义 ResponseErrorHandler
的需求,因此也不需要返回不明确类型的对象。传递错误的方法可以捕获 HttpStatusCodeException
,尝试刷新访问 token ,然后通过递归再次调用自身。需要一个计数器来防止无休止的递归,但它可以传递而不是作为类变量。
缺点是它仍然需要在类中散布错误管理逻辑以及大量样板代码,但它比其他选项简洁得多。
public UserOrders getUserOrders(User user, Integer methodCallCount) {
methodCallCount++;
UserOrders userOrders;
try {
userOrders = restTemplate.postForObject(USER_ORDERS_URI, request, UserOrders.class);
} catch (RestClientException ex) {
APIError apiError = new ObjectMapper().readValue(response.getBody(), APIError.class);
if (methodCallCount < MAX_METHOD_CALLS) {
if (apiError.isType(ACCESS_TOKEN_EXPIRED)) {
refreshVendorAccessTokenInfo(user);
userOrders = getUserOrders(user, methodCallCount);
}
}
}
return userOrders;
}
关于java - RestTemplate 和 ResponseErrorHandler : Elegant means of handling errors given an indeterminate return object,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51498289/