1. 概述

在实际开发中,调用第三方 REST 接口是家常便饭。而 Spring 的 RestTemplate 虽然使用简单,但默认的错误处理机制比较“粗暴”——一旦遇到 4xx 或 5xx 状态码,直接抛异常。

本文将带你实现一个可复用的自定义错误处理器,通过实现 ResponseErrorHandler 接口,把原始的 HTTP 错误转换成业务层面更有意义的异常,避免到处写重复的 try/catch

✅ 核心目标:统一处理远程接口调用的错误,提升代码可维护性。

2. 默认错误处理机制

RestTemplate 在遇到 HTTP 错误时,默认行为如下:

  • HttpClientErrorException:4xx 客户端错误(如 404、400)
  • HttpServerErrorException:5xx 服务端错误(如 500、503)
  • ⚠️ UnknownHttpStatusCodeException:遇到不认识的状态码

这些异常都继承自 RestClientResponseException

💡 最简单的处理方式?当然是 try/catch。但问题是:
当你的项目调用十几个外部接口,每个方法都来一套 try/catch,代码会变得极其冗余且难以维护
踩坑警告:这种写法初期看似省事,后期改起来要命。

所以,我们需要一个全局、可复用的错误处理方案

3. 实现 ResponseErrorHandler

要想接管错误处理流程,必须实现 ResponseErrorHandler 接口。它有两个核心方法:

  • hasError():判断响应是否为错误状态
  • handleError():定义如何处理错误

自定义错误处理器实现

@Component
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {

    @Override
    public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
        return httpResponse.getStatusCode().is5xxServerError() || 
            httpResponse.getStatusCode().is4xxClientError();
    }

    @Override
    public void handleError(ClientHttpResponse httpResponse) throws IOException {
        if (httpResponse.getStatusCode().is5xxServerError()) {
            // 5xx 错误统一包装为 HttpClientErrorException
            throw new HttpClientErrorException(httpResponse.getStatusCode());
        } else if (httpResponse.getStatusCode().is4xxClientError()) {
            // 4xx 错误按需细分处理
            if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
                throw new NotFoundException(); // 自定义业务异常
            }
            // 其他 4xx 可继续扩展
        }
    }
}

📌 关键点说明:

  • hasError() 判断 4xx 和 5xx 都算错误,符合常规认知
  • handleError() 中可根据具体状态码抛出业务级异常,比如 NotFoundException
  • ⚠️ 注意:NotFoundException 需提前定义好,不要用 Spring 自带的,避免语义混淆

注入到 RestTemplate

使用 RestTemplateBuilder 构建实例,并注入自定义处理器:

@Service
public class BarConsumerService {

    private final RestTemplate restTemplate;

    @Autowired
    public BarConsumerService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder
          .errorHandler(new RestTemplateResponseErrorHandler())
          .build();
    }

    public Bar fetchBarById(String barId) {
        return restTemplate.getForObject("/bars/4242", Bar.class);
    }
}

📌 优势:

  • ✅ 处理器复用:所有通过该 RestTemplate 发起的请求都会走自定义逻辑
  • ✅ 解耦清晰:业务方法无需关心错误处理细节
  • ✅ 易于测试:错误逻辑集中在一处,便于 Mock 和验证

4. 测试验证

光写不测等于没写。我们用 MockRestServiceServer 模拟一个返回 404 的场景,验证是否正确抛出 NotFoundException

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { NotFoundException.class, Bar.class })
@RestClientTest
public class RestTemplateResponseErrorHandlerIntegrationTest {

    @Autowired 
    private MockRestServiceServer server;
 
    @Autowired 
    private RestTemplateBuilder builder;

    @Test
    public void givenRemoteApiCall_when404Error_thenThrowNotFound() {
        Assertions.assertNotNull(this.builder);
        Assertions.assertNotNull(this.server);

        RestTemplate restTemplate = this.builder
          .errorHandler(new RestTemplateResponseErrorHandler())
          .build();

        this.server
          .expect(ExpectedCount.once(), requestTo("/bars/4242"))
          .andExpect(method(HttpMethod.GET))
          .andRespond(withStatus(HttpStatus.NOT_FOUND));

        Assertions.assertThrows(NotFoundException.class, () -> {
            Bar response = restTemplate.getForObject("/bars/4242", Bar.class);
        });
    }
}

📌 测试要点:

  • ✅ 使用 @RestClientTest 加载最小化上下文,提升测试速度
  • MockRestServiceServer 模拟远程接口行为,避免依赖真实服务
  • ✅ 断言抛出预期异常,确保错误处理器生效

5. 总结

通过实现 ResponseErrorHandler,我们可以:

  • ✅ 统一处理所有远程调用的 HTTP 错误
  • ✅ 将原始状态码转换为语义清晰的业务异常
  • ✅ 避免在每个服务方法中重复写 try/catch
  • ✅ 提升代码可读性和可维护性

🔗 示例代码已上传至 GitHub:https://github.com/example/spring-resttemplate-error-handling

📌 最佳实践建议:

  • ResponseErrorHandler 做成通用组件,在多个微服务中复用
  • 结合日志记录,方便排查问题
  • 对于 5xx 错误,可考虑加入重试机制(配合 Spring Retry)

原始标题:Spring RestTemplate Error Handling | Baeldung