【问题标题】:Spring Resttemplate exception handlingSpring Resttemplate 异常处理
【发布时间】:2016-10-31 19:04:47
【问题描述】:

下面是代码sn-p;基本上,当错误代码不是 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 中吗?那么代码的目的是什么?

【问题讨论】:

  • 欢迎分享ApplicationException()的代码

标签: spring rest exception-handling resttemplate


【解决方案1】:

您想创建一个实现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
  }
}

看看它的source code,了解 Spring 如何处理 HTTP 错误。

【讨论】:

  • 我有 1 个 RestTemplate 实例,可重复用于不同的调用。我需要以不同的方式处理来自不同调用的错误 - 显然没有办法使用全局处理程序来做到这一点 - 我需要为每个请求提供一个处理程序。
  • 有了这个错误处理程序,我总是得到一个ResourceAccessException,因为RestTemplate.doExecute 捕获IOExceptions 并抛出一个ResourceAccessException。我做错了什么?
  • 我可以通过扩展 DefaultResponseErrorHandler 来解决这个问题。
  • @CrengutaS DefaultResponseErrorHandler 的 handleError 方法被调用,但最终 HttpClientErrorException 被传播到客户端。你在处理程序中是如何处理的?
【解决方案2】:

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

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

【讨论】:

【解决方案3】:

您应该捕获HttpStatusCodeException 异常:

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

【讨论】:

  • IMO 响应应始终带有适当的状态代码,否则代码的目的是什么。
  • 我不确定是否理解@vaibhav 的反对意见:捕获 HttpStatusCodeException 不是因为错误的代码,而是因为在许多情况下总是会抛出异常,所以你的 if(code==value) 永远不会执行。
  • 异常在 Java 中是非常昂贵的。偶尔的、意外的原因(因此得名)是可以的,但除此之外,您应该寻找其他解决方案。
  • “非常昂贵”?比进行 HTTP 调用更昂贵?
  • @RaffaelBecharaRameh - HttpStatusCodeException .getResponseBodyAsString() 或 HttpStatusCodeException.getResponseBodyAsByteArray()。
【解决方案4】:

另一种解决方案是“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 
}

【讨论】:

  • 您需要使用与默认 SDK 不同的 HttpClient 来获取错误响应体(例如 apache commons HttpClient)
【解决方案5】:

Spring 将您从非常非常大的 http 状态代码列表中抽象出来。这就是例外的想法。看看 org.springframework.web.client.RestClientException 层次结构:

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

DefaultResponseErrorHandler 非常简单和可靠。如果响应状态码不是 2xx 系列,它只是为 hasError 方法返回 true。

【讨论】:

  • 老兄,谢谢你的解释。您是如何使用异常层次结构构建这棵树的?
  • 嘿伙计,我用的是 Eclipse。只需按 control+shift+T 打开类型搜索器,然后输入 RestClientException。从结果中双击 RestClientException,Eclipse 将为您打开该类。然后,将鼠标光标放在类名上(其中显示“public class RestClientException...”,然后按 control+T。您将看到该层次结构。
  • 你试过了吗?
  • Btw in Intellij 是:单击项目树中的类并 Ctrl + Alt + U,或鼠标右键单击 -> 构建图
【解决方案6】:

我已经处理如下:

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

【讨论】:

    【解决方案7】:

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

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

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

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

    【讨论】:

      【解决方案8】:

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

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

      【讨论】:

        【解决方案9】:

        兑换码如下

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

        谢谢:)

        【讨论】:

        • 'org.springframework.web.client.HttpClientErrorException' 是 'org.springframework.web.client.HttpStatusCodeException' 的子类。如果您已经捕获 HttpStatusCodeException 并且在处理上述两个异常时没有做任何不同的事情,则不需要捕获 HttpClientErrorException。
        【解决方案10】:

        要扩展@carcaret 的答案......

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

        作为示例,如果发生错误,您的 API 可能会返回如下内容:

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

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

        首先定义一个类用于映射错误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);
        

        【讨论】:

          【解决方案11】:

          这是我的 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;
          }
          

          【讨论】:

          • 您需要使用与默认 SDK 不同的 HttpClient 来获取错误响应体(例如 apache commons HttpClient)
          【解决方案12】:

          我通过覆盖 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();
              }
          

          【讨论】:

            【解决方案13】:

            尝试使用@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);
            }
            

            【讨论】:

              【解决方案14】:

              在全局异常处理程序中阅读有关全局异常处理的信息,添加以下方法。这会起作用。

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

              【讨论】:

                猜你喜欢
                • 2019-06-02
                • 1970-01-01
                • 2019-11-07
                • 1970-01-01
                • 2022-01-24
                • 1970-01-01
                • 2015-07-17
                • 2023-03-28
                • 2017-01-27
                相关资源
                最近更新 更多