Spring Rest模板异常处理

Spring Resttemplate exception handling

下面是代码片段;基本上,当错误代码不是 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 中包装其余的模板交换方法吗?那么代码的目的是什么?

你应该捕获一个 HttpStatusCodeException 异常:

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

您想创建一个实现 ResponseErrorHandler 的 class,然后使用它的一个实例来设置其余模板的错误处理:

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 具有 class DefaultResponseErrorHandler,如果您只想覆盖 handleError 方法,您可以扩展它而不是实现接口。

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

查看其 source code 以了解 Spring 如何处理 HTTP 错误。

如果您在 RestTemplate 中使用池(http 客户端工厂)或负载平衡 (eureka) 机制,您将无法为每个 class 创建一个 new RestTemplate。如果您调用的服务不止一项,则不能使用 setErrorHandler,因为 if 将全局用于您的所有请求。

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

您唯一的其他选择是使用 @Qualifier 注释定义多个 RestTemplate 实例。

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

另一种解决方案是 "enlian" 在此 post 末尾描述的解决方案: 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 
}

这是我的 POST 使用 HTTPS 的方法,它 returns 用于任何类型的不良响应的响应主体。

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());
    }

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

兑换码如下:

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;
    }

这里是代码说明:

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

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

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 库。 这是 gradle : 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 方法 returns 为真。

我是这样处理的:

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 方法修复了它 class:

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

并且您需要为重新模板 bean 设置此处理程序:

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

稍微扩展一下@carcaret 的回答....

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

作为示例,如果发生错误,您的 API 可能会 return 类似这样的事情:

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

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

先定义一个class用于映射错误ro对象

//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。这允许您只处理一次异常,并在一个地方处理所有 'custom' 异常。

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());
}

这是在 Rest 模板中处理异常的方法

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