42

我有一个休息服务,当找不到资源时会发送 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);
    }    
}

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

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

我收到此异常:

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

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

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

4

6 回答 6

47

据我所知,您无法获得实际的 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());
}
于 2014-04-13T04:26:50.280 回答
19

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

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

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

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

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

这是我在上一个项目中解决这个问题的方法。可能有更好的方法,我的解决方案根本没有提取 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;
   }

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

创建其余模板时:

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

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

    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");
    }
于 2014-11-06T18:52:04.323 回答
10

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

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

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

它实际上有一个方法:

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

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

private static class NoOpResponseErrorHandler extends 
       DefaultResponseErrorHandler {

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

}
于 2018-05-06T18:31:46.830 回答
4

您可以创建自己的 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());
        }
    }
}
于 2017-11-28T13:04:01.520 回答
1

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

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);
}
于 2017-08-26T12:22:23.470 回答
0

Spring框架中没有实现ResponseErrorHandler这样的类,所以我只是声明了一个bean:

@Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplateBuilder()
                .errorHandler(new DefaultResponseErrorHandler() {
                    @Override
                    public void handleError(ClientHttpResponse response) throws IOException {
                        //do nothing
                    }
                })
                .build();
    }
于 2021-02-03T09:35:26.950 回答