使用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访问令牌可能已过期。然后,我们应该在重新尝试refreshAccessToken()
之前对getUserOrders()
进行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逻辑现在在另一个类中
谢谢您的建议。
答案 0 :(得分:2)
回答我自己的问题:事实证明,问题本身存在谬论。
我之所以实现ResponseErrorHandler
,是因为我认为我需要它来解析响应,即使该响应返回了HTTP错误代码也是如此。实际上,事实并非如此。
This answer演示了可以通过捕获HttpStatusCodeException
或使用其他标准RestTemplate
将响应解析为一个对象。这就不需要自定义ResponseErrorHandler
,因此不需要返回模棱两可类型的对象。传递给错误的方法可以捕获HttpStatusCodeException
,尝试刷新访问令牌,然后通过递归再次调用自身。需要一个计数器来防止无限递归,但是可以通过它而不是一个类变量来实现。
缺点是它仍然需要在类中散布错误管理逻辑以及大量样板代码,但是比其他选项要整洁。
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;
}