如何在客户端中处理RestTemplate的各类异常?
解决RestTemplate同时处理HTTP响应错误与连接类异常的问题
核心原因
ResponseErrorHandler仅负责处理已收到HTTP响应但状态码不符合预期的场景(比如400、500),而像ResourceAccessException(连接拒绝、超时)这类异常属于请求发送阶段失败,根本没有生成HTTP响应,因此不会触发ErrorHandler的hasError和handleError方法。
解决方案
方案1:封装RestTemplate调用,统一捕获转换异常
在客户端库中编写封装类,把RestTemplate的所有调用方法(getForObject、postForEntity等)包裹起来,在封装逻辑里捕获所有RestClientException(包含HTTP响应异常和连接异常),统一映射为自定义异常。
示例代码:
@Service public class ThirdPartyApiClient { private final RestTemplate restTemplate; public ThirdPartyApiClient(RestTemplate restTemplate) { this.restTemplate = restTemplate; } public <T> T get(String url, Class<T> responseType, Object... uriVars) { try { return restTemplate.getForObject(url, responseType, uriVars); } catch (RestClientException e) { throw convertToCustomException(e); } } public <T> T post(String url, Object request, Class<T> responseType) { try { return restTemplate.postForObject(url, request, responseType); } catch (RestClientException e) { throw convertToCustomException(e); } } private CustomException convertToCustomException(RestClientException e) { if (e instanceof HttpClientErrorException httpEx) { // 复用原ErrorHandler中对HTTP状态码的处理逻辑 String message = String.format("下游服务返回错误:状态码[%s],响应内容[%s]", httpEx.getStatusCode(), httpEx.getResponseBodyAsString()); return new CustomException(message, e); } else if (e instanceof ResourceAccessException resEx) { Throwable rootCause = resEx.getCause(); if (rootCause instanceof ConnectException) { return new CustomException("下游服务未启动或连接拒绝", resEx); } else if (rootCause instanceof SocketTimeoutException) { return new CustomException("请求下游服务超时", resEx); } return new CustomException("资源访问异常", resEx); } return new CustomException("第三方服务调用失败", e); } }
方案2:使用AOP拦截RestTemplate调用
如果不想逐个封装RestTemplate方法,可以用Spring AOP拦截所有RestTemplate的执行方法,统一捕获异常并转换。
示例代码:
@Aspect @Component public class RestTemplateExceptionInterceptor { @Around("execution(* org.springframework.web.client.RestTemplate.*(..))") public Object interceptRestTemplateCalls(ProceedingJoinPoint joinPoint) throws Throwable { try { return joinPoint.proceed(); } catch (RestClientException e) { throw convertToCustomException(e); } } private CustomException convertToCustomException(RestClientException e) { // 同方案1的异常映射逻辑 if (e instanceof HttpClientErrorException httpEx) { return new CustomException(String.format("HTTP错误[%s]: %s", httpEx.getStatusCode(), httpEx.getResponseBodyAsString()), e); } else if (e instanceof ResourceAccessException resEx) { if (resEx.getCause() instanceof ConnectException) { return new CustomException("无法连接下游服务,请检查服务状态", resEx); } else if (resEx.getCause() instanceof SocketTimeoutException) { return new CustomException("请求超时", resEx); } return new CustomException("资源访问失败", resEx); } return new CustomException("调用第三方服务出错", e); } }
注意事项
- 若原
CustomRestTemplateErrorHandler还需要处理响应体中的错误信息(比如解析错误响应的业务码),可以保留它,但此时HttpClientErrorException会先被ErrorHandler抛出,再被封装类/AOP捕获转换,逻辑不冲突。 - 自定义异常最好继承
RuntimeException,避免上层代码强制捕获,简化处理逻辑。
内容的提问来源于stack exchange,提问作者CurvedHalo
相关产品推荐
相关产品推荐

