«1. Обзор
В этом кратком руководстве мы обсудим, как реализовать и внедрить интерфейс ResponseErrorHandler в экземпляр RestTemplate, чтобы изящно обрабатывать ошибки HTTP, возвращаемые удаленными API.
2. Обработка ошибок по умолчанию
По умолчанию RestTemplate выдает одно из следующих исключений в случае ошибки HTTP:
- HttpClientErrorException – in case of HTTP status 4xx
- HttpServerErrorException – in case of HTTP status 5xx
- UnknownHttpStatusCodeException – in case of an unknown HTTP status
Все эти исключения являются расширениями RestClientResponseException.
Очевидно, что простейшая стратегия добавления пользовательской обработки ошибок состоит в том, чтобы обернуть вызов в блок try/catch. Затем мы обрабатываем пойманное исключение по своему усмотрению.
Однако эта простая стратегия плохо масштабируется по мере увеличения количества удаленных API или вызовов. Было бы более эффективно, если бы мы могли реализовать повторно используемый обработчик ошибок для всех наших удаленных вызовов.
3. Реализация ResponseErrorHandler
Итак, класс, который реализует ResponseErrorHandler, будет считывать статус HTTP из ответа и либо:
- Throw an exception that is meaningful to our application
- Simply ignore the HTTP status and let the response flow continue without interruption
Нам нужно внедрить реализацию ResponseErrorHandler в экземпляр RestTemplate.
Следовательно, мы используем RestTemplateBuilder для создания шаблона и замены DefaultResponseErrorHandler в потоке ответов.
Итак, давайте сначала реализуем наш RestTemplateResponseErrorHandler:
@Component
public class RestTemplateResponseErrorHandler
implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse httpResponse)
throws IOException {
return (
httpResponse.getStatusCode().series() == CLIENT_ERROR
|| httpResponse.getStatusCode().series() == SERVER_ERROR);
}
@Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
if (httpResponse.getStatusCode()
.series() == HttpStatus.Series.SERVER_ERROR) {
// handle SERVER_ERROR
} else if (httpResponse.getStatusCode()
.series() == HttpStatus.Series.CLIENT_ERROR) {
// handle CLIENT_ERROR
if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new NotFoundException();
}
}
}
}
Затем мы создаем экземпляр RestTemplate, используя RestTemplateBuilder, чтобы представить наш RestTemplateResponseErrorHandler:
@Service
public class BarConsumerService {
private RestTemplate restTemplate;
@Autowired
public BarConsumerService(RestTemplateBuilder restTemplateBuilder) {
RestTemplate restTemplate = restTemplateBuilder
.errorHandler(new RestTemplateResponseErrorHandler())
.build();
}
public Bar fetchBarById(String barId) {
return restTemplate.getForObject("/bars/4242", Bar.class);
}
}
4. Тестирование нашей реализации
Наконец, давайте проверим это обработчик, имитируя сервер и возвращая статус NOT_FOUND:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { NotFoundException.class, Bar.class })
@RestClientTest
public class RestTemplateResponseErrorHandlerIntegrationTest {
@Autowired
private MockRestServiceServer server;
@Autowired
private RestTemplateBuilder builder;
@Test(expected = NotFoundException.class)
public void givenRemoteApiCall_when404Error_thenThrowNotFound() {
Assert.assertNotNull(this.builder);
Assert.assertNotNull(this.server);
RestTemplate restTemplate = this.builder
.errorHandler(new RestTemplateResponseErrorHandler())
.build();
this.server
.expect(ExpectedCount.once(), requestTo("/bars/4242"))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.NOT_FOUND));
Bar response = restTemplate
.getForObject("/bars/4242", Bar.class);
this.server.verify();
}
}
5. Заключение
В этой статье представлено решение для реализации и тестирования пользовательского обработчика ошибок для RestTemplate, который преобразует ошибки HTTP в осмысленные исключения.
Как всегда, код, представленный в этой статье, доступен на Github.