You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在客户端中处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 09:52:34