Spring Boot中RestTemplate与异常处理器的异常信息获取问题
解决RestTemplate调用时无法获取对方服务异常详情的问题
我完全懂你的困扰:用RestTemplate调用其他服务时,对方抛出异常返回的ErrorDetails自定义JSON被默认的错误处理器吞掉了,只能拿到笼统的HttpServerErrorException,还陷入了postForLocation能拿location但异常信息拿不到,postForObject能处理响应但拿不到location的两难处境。下面给你两个针对性的解决方案:
方案一:自定义RestTemplate错误处理器,解析异常响应体
RestTemplate默认的DefaultResponseErrorHandler会把非成功状态码(非2xx/3xx)直接包装成Spring自带的HTTP异常,不会解析响应体里的自定义错误信息。我们可以自定义一个错误处理器,把响应体解析成ErrorDetails,再抛出携带这些信息的自定义异常。
步骤1:实现自定义ResponseErrorHandler
import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.DefaultResponseErrorHandler; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; public class CustomResponseErrorHandler extends DefaultResponseErrorHandler { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public void handleError(ClientHttpResponse response) throws IOException { // 先解析响应体中的ErrorDetails ErrorDetails errorDetails = objectMapper.readValue(response.getBody(), ErrorDetails.class); // 抛出自定义异常,携带错误详情和状态码 throw new RemoteServiceException("远程服务调用失败", errorDetails, response.getStatusCode()); } } // 自定义异常类,用来传递ErrorDetails信息 public class RemoteServiceException extends RuntimeException { private final ErrorDetails errorDetails; private final HttpStatus statusCode; public RemoteServiceException(String message, ErrorDetails errorDetails, HttpStatus statusCode) { super(message); this.errorDetails = errorDetails; this.statusCode = statusCode; } // 提供getter方法获取错误详情 public ErrorDetails getErrorDetails() { return errorDetails; } public HttpStatus getStatusCode() { return statusCode; } }
步骤2:配置RestTemplate使用自定义错误处理器
别每次调用都新建RestTemplate,这是不良实践!应该把它配置成Bean统一注入:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(new CustomResponseErrorHandler()); return restTemplate; } }
步骤3:在Controller中使用配置好的RestTemplate
@RestController public class YourController { private final RestTemplate restTemplate; // 构造注入配置好的RestTemplate实例 public YourController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @PostMapping public ResponseEntity doSmth(@RequestBody String json){ HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); String url = "..."; HttpEntity<String> requestEntity = new HttpEntity<>("body", headers); try { URI location = restTemplate.postForLocation(url, requestEntity); return ResponseEntity.created(location).build(); } catch (RemoteServiceException e) { // 这里就能拿到对方返回的具体ErrorDetails了 ErrorDetails errorDetails = e.getErrorDetails(); // 直接返回错误详情给客户端,或者做其他业务处理 return new ResponseEntity<>(errorDetails, e.getStatusCode()); } } }
方案二:使用RestTemplate.exchange方法,统一处理成功/失败响应
postForLocation和postForObject都是exchange方法的封装,exchange更灵活——它不会自动抛出异常,而是返回包含完整响应信息(状态码、headers、body)的ResponseEntity,不管响应状态码是什么。这样你可以同时处理成功时获取location、失败时解析ErrorDetails的场景。
代码示例:
@PostMapping public ResponseEntity doSmth(@RequestBody String json){ HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); String url = "..."; HttpEntity<String> requestEntity = new HttpEntity<>("body", headers); // 使用exchange方法,指定请求方式和响应体类型 ResponseEntity<String> response = restTemplate.exchange( url, HttpMethod.POST, requestEntity, String.class ); if (response.getStatusCode().is2xxSuccessful()) { // 成功时从headers中获取location URI location = response.getHeaders().getLocation(); return ResponseEntity.created(location).build(); } else { // 失败时解析响应体为ErrorDetails ObjectMapper objectMapper = new ObjectMapper(); ErrorDetails errorDetails = objectMapper.readValue(response.getBody(), ErrorDetails.class); return new ResponseEntity<>(errorDetails, response.getStatusCode()); } }
如果想直接把响应体绑定到ErrorDetails,可以调整exchange的响应类型,注意成功时响应体为空的情况:
ResponseEntity<ErrorDetails> response = restTemplate.exchange( url, HttpMethod.POST, requestEntity, ErrorDetails.class ); if (response.getStatusCode().is2xxSuccessful()) { URI location = response.getHeaders().getLocation(); return ResponseEntity.created(location).build(); } else { return new ResponseEntity<>(response.getBody(), response.getStatusCode()); }
补充说明
- 之前用
postForObject拿不到location,是因为postForObject的设计目标就是只返回响应体,headers不会被暴露,只有exchange会返回完整的ResponseEntity包含headers信息。 - 尽量不要每次调用都新建
RestTemplate,统一配置成Bean可以更好地管理错误处理器、消息转换器等组件。
内容的提问来源于stack exchange,提问作者Donatello
相关产品推荐
相关产品推荐

