Spring MVC - 发生 http 404 时 RestTemplate 启动异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16194014/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 05:53:44  来源:igfitidea点击:

Spring MVC - RestTemplate launch exception when http 404 happens

springrestresttemplate

提问by loic

I have a rest service which send an 404 error when the resources is not found. Here the source of my controller and the exception which send Http 404.

我有一个休息服务,当找不到资源时,它会发送 404 错误。这里是我的控制器的来源和发送 Http 404 的异常。

@Controller
@RequestMapping("/site")
public class SiteController
{

    @Autowired
    private IStoreManager storeManager;

    @RequestMapping(value = "/stores/{pkStore}", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public StoreDto getStoreByPk(@PathVariable long pkStore) {       
        Store s = storeManager.getStore(pkStore);
        if (null == s) {
            throw new ResourceNotFoundException("no store with pkStore : " + pkStore);
        }
        return StoreDto.entityToDto(s);       

    }
}

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException
{       
    private static final long serialVersionUID = -6252766749487342137L;    
    public ResourceNotFoundException(String message) {
        super(message);
    }    
}

When i try to call it with RestTemplate with this code :

当我尝试使用以下代码使用 RestTemplate 调用它时:

ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
 System.out.println(r.getStatusCode());
 System.out.println(r.getBody());

I receive this exception :

我收到此异常:

org.springframework.web.client.RestTemplate handleResponseError
ATTENTION: GET request for "http://........./stores/99" resulted in 404 (Introuvable); invoking error handler
org.springframework.web.client.HttpClientErrorException: 404 Introuvable

I was thinking I can explore my responseEntity Object and do some things with the statusCode. But exception is launch and my app go down.

我想我可以探索我的 responseEntity 对象并使用 statusCode 做一些事情。但例外是启动,我的应用程序关闭。

Is there a specific configuration for restTemplate to not send exception but populate my ResponseEntity.

是否有针对 restTemplate 的特定配置,以不发送异常但填充我的 ResponseEntity。

Thanks very much for help.

非常感谢您的帮助。

--

——

Lo?c

位置

回答by Squatting Bear

As far as I'm aware, you can't get an actual ResponseEntity, but the status code and body (if any) can be obtained from the exception:

据我所知,您无法获得实际的 ResponseEntity,但可以从异常中获取状态代码和正文(如果有):

try {
    ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
}
catch (final HttpClientErrorException e) {
    System.out.println(e.getStatusCode());
    System.out.println(e.getResponseBodyAsString());
}

回答by Dick Chesterwood

RESTTemplate is quite deficient in this area IMO. There's a good blog post here about how you could possibly extract the response body when you've received an error:

RESTTemplate 在这方面 IMO 相当缺乏。这里有一篇很好的博客文章,介绍了在收到错误时如何提取响应正文:

http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

As of today there is an outstanding JIRA request that the template provides the possibility to extract the response body:

截至今天,有一个未完成的 JIRA 请求,模板提供了提取响应正文的可能性:

https://jira.spring.io/browse/SPR-10961

https://jira.spring.io/browse/SPR-10961

The trouble with Squatting Bear's answer is that you would have to interrogate the status code inside the catch block eg if you're only wanting to deal with 404's

Squatting Bear 的答案的问题在于,您必须询问 catch 块内的状态代码,例如,如果您只想处理 404

Here's how I got around this on my last project. There may be better ways, and my solution doesn't extract the ResponseBody at all.

这是我在上一个项目中解决这个问题的方法。可能有更好的方法,我的解决方案根本不提取 ResponseBody。

public class ClientErrorHandler implements ResponseErrorHandler
{
   @Override
   public void handleError(ClientHttpResponse response) throws IOException 
   {
       if (response.getStatusCode() == HttpStatus.NOT_FOUND)
       {
           throw new ResourceNotFoundException();
       }

       // handle other possibilities, then use the catch all... 

       throw new UnexpectedHttpException(response.getStatusCode());
   }

   @Override
   public boolean hasError(ClientHttpResponse response) throws IOException 
   {
       return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
         || response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR;
   }

The ResourceNotFoundException and UnexpectedHttpException are my own unchecked exceptions.

ResourceNotFoundException 和 UnexpectedHttpException 是我自己的未经检查的异常。

The when creating the rest template:

创建其余模板时:

    RestTemplate template = new RestTemplate();
    template.setErrorHandler(new ClientErrorHandler());

Now we get the slightly neater construct when making a request:

现在我们在发出请求时得到了稍微简洁的构造:

    try
    {
        HttpEntity response = template.exchange("http://localhost:8080/mywebapp/customer/100029",
                                        HttpMethod.GET, requestEntity, String.class);
        System.out.println(response.getBody());
    }
    catch (ResourceNotFoundException e)
    {
        System.out.println("Customer not found");
    }

回答by yuranos

Since it's 2018 and I hope that when people say "Spring" they actually mean "Spring Boot" at least, I wanted to expand the given answers with a less dust-covered approach.

由于现在是 2018 年,我希望当人们说“Spring”时,他们至少实际上是指“Spring Boot”,我想用一种不那么尘土飞扬的方法来扩展给定的答案。

Everything mentioned in the previous answers is correct - you need to use a custom ResponseErrorHandler. Now, in Spring Boot world the way to configure it is a bit simpler than before. There is a convenient class called RestTemplateBuilder. If you read the very first line of its java doc it says:

前面答案中提到的所有内容都是正确的 - 您需要使用自定义ResponseErrorHandler. 现在,在 Spring Boot 世界中,配置它的方式比以前简单一些。有一个方便的类叫做RestTemplateBuilder. 如果您阅读其 java 文档的第一行,它会说:

Builder that can be used to configure and create a RestTemplate. Provides convenience methods to register converters, error handlersand UriTemplateHandlers.

可用于配置和创建 RestTemplate 的构建器。提供方便的方法来注册转换器、错误处理程序和 UriTemplateHandlers。

It actually has a method just for that:

它实际上有一个方法:

new RestTemplateBuilder().errorHandler(new DefaultResponseErrorHandler()).build();

On top of that, Spring guys realized the drawbacks of a conventional RestTemplatelong time ago, and how it can be especially painful in tests. They created a convenient class, TestRestTemplate, which serves as a wrapper around RestTemplateand set its errorHandler to an empty implementation:

最重要的是,Spring 人员RestTemplate很久以前就意识到了传统的缺点,以及它在测试中是如何特别痛苦的。他们创建了一个方便的类 ,TestRestTemplate作为一个包装器RestTemplate,并将其 errorHandler 设置为一个空的实现:

private static class NoOpResponseErrorHandler extends 
       DefaultResponseErrorHandler {

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

}

回答by siledh

You can create your own RestTemplate wrapper which does not throw exceptions, but returns a response with the received status code. (You could also return the body, but that would stop being type-safe, so in the code below the body remains simply null.)

您可以创建自己的 RestTemplate 包装器,该包装器不会引发异常,但会返回带有接收到的状态代码的响应。(您也可以返回主体,但这将不再是类型安全的,因此在下面的代码中主体仍然是简单的null。)

/**
 * A Rest Template that doesn't throw exceptions if a method returns something other than 2xx
 */
public class GracefulRestTemplate extends RestTemplate {
    private final RestTemplate restTemplate;

    public GracefulRestTemplate(RestTemplate restTemplate) {
        super(restTemplate.getMessageConverters());
        this.restTemplate = restTemplate;
    }

    @Override
    public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
        return withExceptionHandling(() -> restTemplate.getForEntity(url, responseType));
    }

    @Override
    public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
        return withExceptionHandling(() -> restTemplate.postForEntity(url, request, responseType));
    }

    private <T> ResponseEntity<T> withExceptionHandling(Supplier<ResponseEntity<T>> action) {
        try {
            return action.get();
        } catch (HttpClientErrorException ex) {
            return new ResponseEntity<>(ex.getStatusCode());
        }
    }
}

回答by Emmanuel Osimosu

Recently had a usecase for this. My solution:

最近有一个用例。我的解决方案:

public class MyErrorHandler implements ResponseErrorHandler {

@Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
    return hasError(clientHttpResponse.getStatusCode());
}

@Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
    HttpStatus statusCode = clientHttpResponse.getStatusCode();
    MediaType contentType = clientHttpResponse
        .getHeaders()
        .getContentType();
    Charset charset = contentType != null ? contentType.getCharset() : null;
    byte[] body = FileCopyUtils.copyToByteArray(clientHttpResponse.getBody());

    switch (statusCode.series()) {
        case CLIENT_ERROR:
            throw new HttpClientErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
        case SERVER_ERROR:
            throw new HttpServerErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
        default:
            throw new RestClientException("Unknown status code [" + statusCode + "]");
    }

}

private boolean hasError(HttpStatus statusCode) {
    return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
        statusCode.series() == HttpStatus.Series.SERVER_ERROR);
}