1. Обзор
В этом кратком руководстве мы обсудим, как реализовать и внедрить интерфейс ResponseErrorHandler в экземпляр
RestTemplate
для корректной обработки ошибок HTTP, возвращаемых удаленными API.
2. Обработка ошибок по умолчанию
По умолчанию RestTemplate выдает
одно из этих исключений в случае ошибки HTTP:
HttpClientErrorException
— в случае HTTP-статуса 4xxHttpServerErrorException —
в случае HTTP-статуса 5xxUnknownHttpStatusCodeException —
в случае неизвестного статуса HTTP
Все эти исключения являются расширениями RestClientResponseException
.
Очевидно, что простейшей стратегией добавления пользовательской обработки ошибок является помещение вызова в блок try/catch
. Затем мы можем обработать пойманное исключение по своему усмотрению.
Однако эта простая стратегия плохо масштабируется по мере увеличения количества удаленных API или вызовов. Было бы более эффективно, если бы мы могли реализовать повторно используемый обработчик ошибок для всех наших удаленных вызовов.
3. Реализация ResponseErrorHandler
Класс, реализующий ResponseErrorHandler
, будет считывать статус HTTP из ответа и либо:
- Выбросить исключение, которое имеет смысл для нашего приложения
- Просто игнорируйте статус HTTP и продолжайте поток ответов без перерыва.
Нам нужно внедрить реализацию 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
:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { NotFoundException.class, Bar.class })
@RestClientTest
public class RestTemplateResponseErrorHandlerIntegrationTest {
@Autowired
private MockRestServiceServer server;
@Autowired
private RestTemplateBuilder builder;
@Test
public void givenRemoteApiCall_when404Error_thenThrowNotFound() {
Assertions.assertNotNull(this.builder);
Assertions.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));
Assertions.assertThrows(NotFoundException.class, () -> {
Bar response = restTemplate.getForObject("/bars/4242", Bar.class);
});
}
}
5. Вывод
В этой статье мы представили решение для реализации и тестирования пользовательского обработчика ошибок для RestTemplate
, который преобразует ошибки HTTP в осмысленные исключения.
Как всегда, код, представленный в этой статье, доступен на Github .