java - RestTemplate 和 ResponseErrorHandler : Elegant means of handling errors given an indeterminate return object

标签 java spring-boot error-handling resttemplate

使用 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/

相关文章:

java - Spring Boot Oauth2 扩展 DefaultTokenServices

java - 运行 java -jar 并指定配置路径

java - 元空间是从 native 内存中分配的吗?

java - Foursquare Venue API 返回空 field 列表

java - 在不同的 Tomcat Web 应用程序之间共享凭据

java - struts2 - 理解值(value)栈

java - 无法通过 @Autowired 将 AuthenticationManager 传递给自定义过滤器

c - 如何正确使用套接字的 write() 函数?

Swift Combine 接收器在第一次错误后停止接收值

javascript - 无法在使用 .append() 添加的图像上调用 jQuery .error()