Spring MVC - исключение запуска RestTemplate, когда 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);
    }    
}

Когда я пытаюсь вызвать его с помощью 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.

Большое спасибо за помощь.

-

Лоик

Ответ 1

Насколько я знаю, вы не можете получить фактическую 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());
}

Ответ 2

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 заключается в том, что вам придется запросить код состояния внутри блока catch, например, если вы хотите иметь дело только с 404

Вот как я обошел это на моем последнем проекте. Могут быть и лучшие способы, и мое решение вообще не извлекает 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");
    }

Ответ 3

Поскольку это 2018 год, и я надеюсь, что, когда люди говорят "Весна", они на самом деле означают "Весенний ботинок", по крайней мере, я хотел расширить данные ответы с менее пылевым подходом.

Все, что упоминалось в предыдущих ответах, является правильным - вам нужно использовать собственный ResponseErrorHandler. Теперь, в Spring Boot world, способ настроить его немного проще, чем раньше. Существует удобный класс RestTemplateBuilder. Если вы прочтете первую строку своего java-документа, он говорит:

Builder, который можно использовать для настройки и создания RestTemplate. Предоставляет удобные методы регистрации преобразователей, обработчиков ошибок и UriTemplateHandlers.

У этого на самом деле есть метод только для этого:

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

Кроме того, весенние парни давно осознали недостатки обычного RestTemplate и как это может быть особенно болезненно в тестах. Они создали удобный класс TestRestTemplate, который служит оберткой вокруг RestTemplate и устанавливает его errorHandler в пустую реализацию:

private static class NoOpResponseErrorHandler extends 
       DefaultResponseErrorHandler {

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

}

Ответ 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());
        }
    }
}

Ответ 5

В последнее время у нас есть возможность для этого. Мое решение:

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