Spring Resttemplate 异常处理

下面是代码片段; 基本上,当错误代码不是200时,我尝试传播异常。

ResponseEntity<Object> response = restTemplate.exchange(url.toString().replace("{version}", version),
HttpMethod.POST, entity, Object.class);
if(response.getStatusCode().value()!= 200){
logger.debug("Encountered Error while Calling API");
throw new ApplicationException();
}

然而,在服务器响应500的情况下,我得到了异常

org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94) ~[spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]

我真的需要在 try 中包装 rest 模板交换方法吗?那么编码的目的是什么呢?

295092 次浏览

您应该捕获一个 HttpStatusCodeException异常:

try {
restTemplate.exchange(...);
} catch (HttpStatusCodeException exception) {
int statusCode = exception.getStatusCode().value();
...
}

您希望创建一个实现 ResponseErrorHandler的类,然后使用它的一个实例来设置静息模板的错误处理:

public class MyErrorHandler implements ResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// your error handling here
}


@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
...
}
}


[...]


public static void main(String args[]) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new MyErrorHandler());
}

此外,Spring 具有类 DefaultResponseErrorHandler,如果只想重写 handleError方法,可以扩展该类而不是实现接口。

public class MyErrorHandler extends DefaultResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// your error handling here
}
}

看看它的 源代码,了解 Spring 如何处理 HTTP 错误。

如果您使用池(http 客户端工厂)或负载平衡(尤里卡)机制与您的 RestTemplate,您将不会有豪华的创建每个类的 new RestTemplate。如果您正在调用多个服务,则不能使用 setErrorHandler,因为 If 将在全局范围内用于所有请求。

在这种情况下,捕获 HttpStatusCodeException似乎是更好的选择。

另一种选择是使用 @Qualifier注释定义多个 RestTemplate实例。

另外——但这是我自己的口味——我喜欢紧紧依偎在我的电话上的错误处理。

另一个解决方案是“ enlian”在这篇文章的结尾描述的: Http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

try{
restTemplate.exchange(...)
} catch(HttpStatusCodeException e){
String errorpayload = e.getResponseBodyAsString();
//do whatever you want
} catch(RestClientException e){
//no response payload, tell the user sth else
}

下面是我的带 HTTPS 的 POST 方法,它为任何类型的错误响应返回响应体。

public String postHTTPSRequest(String url,String requestJson)
{
//SSL Context
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
//Initiate REST Template
RestTemplate restTemplate = new RestTemplate(requestFactory);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//Send the Request and get the response.
HttpEntity<String> entity = new HttpEntity<String>(requestJson,headers);
ResponseEntity<String> response;
String stringResponse = "";
try {
response = restTemplate.postForEntity(url, entity, String.class);
stringResponse = response.getBody();
}
catch (HttpClientErrorException e)
{
stringResponse = e.getResponseBodyAsString();
}
return stringResponse;
}

Spring 聪明地将 http 错误代码视为异常,并假设异常处理代码具有处理错误的上下文。要让交易所按照你预期的方式运作,可以这样做:

    try {
return restTemplate.exchange(url, httpMethod, httpEntity, String.class);
} catch(HttpStatusCodeException e) {
return ResponseEntity.status(e.getRawStatusCode()).headers(e.getResponseHeaders())
.body(e.getResponseBodyAsString());
}

这将返回响应的所有预期结果。

交易守则如下:

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException

异常 RestClientExceptionHttpClientErrorExceptionHttpStatusCodeException异常。

所以在 RestTemplete中可能会出现 HttpClientErrorExceptionHttpStatusCodeException异常。 在异常对象中,您可以通过以下方式获得准确的错误消息: exception.getResponseBodyAsString()

下面是示例代码 :

public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {


printLog( "Url : " + url);
printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));


try {


RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());




HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);


HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);


long start = System.currentTimeMillis();


ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);


printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());




printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));


long elapsedTime = System.currentTimeMillis() - start;
printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");


if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
return responseEntity.getBody();
}


} catch (HttpClientErrorException exception) {
printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
//Handle exception here
}catch (HttpStatusCodeException exception) {
printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
//Handle exception here
}
return null;
}

下面是代码描述 :

在此方法中,必须传递请求和响应类。此方法将自动将响应解析为所请求的对象。

首先,您必须添加消息转换器。

restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

然后加入 requestHeader。 密码如下:

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);


HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

最后,必须调用交换方法:

ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

为了打印漂亮,我使用了 Gson 图书馆。 这是分级: compile 'com.google.code.gson:gson:2.4'

你可以调用下面的代码来获得响应:

ResponseObject response=new RestExample().callToRestService(HttpMethod.POST,"URL_HERE",new RequestObject(),ResponseObject.class);

下面是完整的工作代码 :

import com.google.gson.GsonBuilder;
import org.springframework.http.*;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;




public class RestExample {


public RestExample() {


}


public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {


printLog( "Url : " + url);
printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));


try {


RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());




HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);


HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);


long start = System.currentTimeMillis();


ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);


printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());




printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));


long elapsedTime = System.currentTimeMillis() - start;
printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");


if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
return responseEntity.getBody();
}


} catch (HttpClientErrorException exception) {
printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
//Handle exception here
}catch (HttpStatusCodeException exception) {
printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
//Handle exception here
}
return null;
}


private void printLog(String message){
System.out.println(message);
}
}

谢谢:)

Spring 把你从一个非常非常庞大的 HTTP状态码列表中抽象出来。这就是例外的概念。查看 org.springframework.web.client。RestClientException 层次结构:

在处理 http 响应时,有许多类可以映射最常见的情况。Http 代码列表非常大,您不会希望编写代码来处理每种情况。但是,例如,查看 HttpClientErrorException 子层次结构。只有一个异常可以映射任何4xx 类型的错误。如果你想深入调查,你可以的。但是只要捕获 HttpClientErrorException,就可以处理向服务提供错误数据的任何情况。

DefaultResponseErrorHandler 非常简单可靠。如果响应状态代码不是来自2xx 系列,那么对于 hasError 方法,它只返回 true。

我的处理方式如下:

try {
response = restTemplate.postForEntity(requestUrl, new HttpEntity<>(requestBody, headers), String.class);
} catch (HttpStatusCodeException ex) {
response = new ResponseEntity<String>(ex.getResponseBodyAsString(), ex.getResponseHeaders(), ex.getStatusCode());
}

一个非常简单的解决方案可以是:

try {
requestEntity = RequestEntity
.get(new URI("user String"));
    

return restTemplate.exchange(requestEntity, String.class);
} catch (RestClientResponseException e) {
return ResponseEntity.status(e.getRawStatusCode()).body(e.getResponseBodyAsString());
}

我通过重写 DefaultResponseErrorHandler 类中的 hasError 方法修复了这个问题:

public class BadRequestSafeRestTemplateErrorHandler extends DefaultResponseErrorHandler
{
@Override
protected boolean hasError(HttpStatus statusCode)
{
if(statusCode == HttpStatus.BAD_REQUEST)
{
return false;
}
return statusCode.isError();
}
}

并且您需要为 restemplate bean 设置这个处理程序:

@Bean
protected RestTemplate restTemplate(RestTemplateBuilder builder)
{
return builder.errorHandler(new BadRequestSafeRestTemplateErrorHandler()).build();
}

把“车牌号”扩大一点回答... 。

考虑到您的响应错误是由 json 消息返回的。例如,API 可以将 204作为状态代码错误返回,将 json 消息作为错误列表返回。在这种情况下,您需要定义应该弹出哪些消息作为错误以及如何使用它们。

作为一个示例,如果出现错误,API 可能会返回类似下面这样的内容:

 { "errorCode":"TSC100" , "errorMessage":"The foo bar error happend" , "requestTime" : "202112827733" .... }

要使用 json 并抛出自定义异常,可以执行以下操作:

首先定义一个用于映射错误对象的类

//just to map the json to object
public class ServiceErrorResponse implements Serializable {


//setter and getters
private Object errorMessage;
private String errorCode;
private String requestTime;
   

}

现在定义错误处理程序:

public class ServiceResponseErrorHandler implements ResponseErrorHandler {


private List<HttpMessageConverter<?>> messageConverters;


@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
        

return (response.getStatusCode().is4xxClientError() ||
response.getStatusCode().is5xxServerError());
}


@Override
public void handleError(ClientHttpResponse response) throws IOException {
        

@SuppressWarnings({ "unchecked", "rawtypes" })
HttpMessageConverterExtractor<ServiceErrorResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ServiceErrorResponse.class, messageConverters);
        

ServiceErrorResponse errorObject = errorMessageExtractor.extractData(response);
        

throw new ResponseEntityErrorException(
ResponseEntity.status(response.getRawStatusCode())
.headers(response.getHeaders())
.body(errorObject)
);
        

}


public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}

自定义的例外将是:

public class ResponseEntityErrorException extends RuntimeException  {
    

private ResponseEntity<ServiceErrorResponse> serviceErrorResponseResponse;


public ResponseEntityErrorException(ResponseEntity<ServiceErrorResponse> serviceErrorResponseResponse) {
this.serviceErrorResponseResponse = serviceErrorResponseResponse;
}
    

public ResponseEntity<ServiceErrorResponse> getServiceErrorResponseResponse() {
return serviceErrorResponseResponse;
}
}

使用方法:

RestTemplateResponseErrorHandler errorHandler = new
RestTemplateResponseErrorHandler();
//pass the messageConverters to errror handler and let it convert json to object
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);

尝试使用 @ControllerAdvice。这使您只能处理一次异常,并在一个地方处理所有“自定义”处理的异常。

Https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/controlleradvice.html

例子

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler{


@ExceptionHandler(MyException.class)
protected ResponseEntity<Object> handleMyException(){
MyException exception,
WebRequest webRequest) {
return handleExceptionInternal(
exception,
exception.getMessage(),
exception.getResponseHeaders(),
exception.getStatusCode(),
webRequest);
}

阅读全局异常处理程序中的全局异常处理。添加以下方法。这将工作。

@ExceptionHandler( {HttpClientErrorException.class, HttpStatusCodeException.class, HttpServerErrorException.class})
@ResponseBody
public ResponseEntity<Object> httpClientErrorException(HttpStatusCodeException e) throws IOException {
BodyBuilder bodyBuilder = ResponseEntity.status(e.getRawStatusCode()).header("X-Backend-Status", String.valueOf(e.getRawStatusCode()));
if (e.getResponseHeaders().getContentType() != null) {
bodyBuilder.contentType(e.getResponseHeaders().getContentType());
}
return bodyBuilder.body(e.getResponseBodyAsString());
}

这是如何处理休息模板中的异常

        try {
return restTemplate.exchange("URL", HttpMethod.POST, entity, String.class);
}
catch (HttpStatusCodeException e)
{
return ResponseEntity.status(e.getRawStatusCode()).headers(e.getResponseHeaders())
.body(e.getResponseBodyAsString());
}

还有一个使用 TestRestTemplate的选项。它对于集成和 E2E 测试非常有用,当您需要手动验证所有状态代码时(例如在负面测试用例中)。

TestRestTemplate 是容错的。这意味着4xx 和5xx 不会导致抛出异常,而是可以通过响应实体及其状态代码检测到异常。