В преддверии старта курса «Разработчик на Spring Framework» подготовили традиционный перевод полезного материала.
Также абсолютно бесплатно предлагаем посмотреть запись демо-урока на тему «Введение в облака, создание кластера в Mongo DB Atlas».
WebClient — это неблокирующий, реактивный клиент для выполнения HTTP-запросов.
Время RestTemplate подошло к концу
Возможно, вы слышали, что время RestTemplate на исходе. Теперь это указано и в официальной документации:
NOTE: As of 5.0 this class is in maintenance mode, with only minor requests for changes and bugs to be accepted going forward. Please, consider using the
org.springframework.web.reactive.client.WebClient
which has a more modern API and supports sync, async, and streaming scenarios.ПРИМЕЧАНИЕ: Начиная с версии 5.0, этот класс законсервирован и в дальнейшем будут приниматься только минорные запросы на изменения и на исправления багов. Пожалуйста, подумайте об использовании
org.springframework.web.reactive.client.WebClient
, который имеет более современный API и поддерживает синхронную, асинхронную и потоковую передачи.
Конечно, мы понимаем, что RestTemplate не исчезнет мгновенно, однако новой функциональности в нем уже не будет. По этой причине в Интернете можно видеть десятки вопросов о том, что такое WebClient и как его использовать. В этой статье вы найдете советы по использованию новой библиотеки.
Отличия между WebClient и RestTemplate
Если в двух словах, то основное различие между этими технологиями заключается в том, что RestTemplate работает синхронно (блокируя), а WebClient работает асинхронно (не блокируя).
RestTemplate — это синхронный клиент для выполнения HTTP-запросов, он предоставляет простой API с шаблонным методом поверх базовых HTTP-библиотек, таких как HttpURLConnection
(JDK), HttpComponents
(Apache) и другими.
Spring WebClient — это асинхронный, реактивный клиент для выполнения HTTP-запросов, часть Spring WebFlux.
Вам, вероятно, интересно, как можно заменить синхронного клиента на асинхронный. У WebClient есть решение этой задачи. Мы рассмотрим несколько примеров использования WebClient.
А сейчас настало время попрощаться с RestTemplate , сказать ему спасибо и продолжить изучение WebClient.
Начало работы с WebClient
Предварительные условия
Spring Boot 2
JDK 11
Подготовка проекта
Давайте создадим базовый проект с зависимостями, используя Spring Initializr.
Теперь взглянем на зависимости нашего проекта. Самая важная для нас зависимость — spring-boot-starter-webflux
.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Spring WebFlux является частью Spring 5 и обеспечивает поддержку реактивного программирования для веб-приложений.
Пришло время настроить WebClient.
Настройка WebClient
Есть несколько способов настройки WebClient. Первый и самый простой — создать его с настройками по умолчанию.
WebClient client = WebClient.create();
Можно также указать базовый URL:
WebClient client = WebClient.create("http://base-url.com");
Третий и самый продвинутый вариант — создать WebClient с помощью билдера. Мы будем использовать конфигурацию, которая включает базовый URL и таймауты.
@Configuration
public class WebClientConfiguration {
private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
public static final int TIMEOUT = 1000;
@Bean
public WebClient webClientWithTimeout() {
final var tcpClient = TcpClient
.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, TIMEOUT)
.doOnConnected(connection -> {
connection.addHandlerLast(new ReadTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS));
connection.addHandlerLast(new WriteTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS));
});
return WebClient.builder()
.baseUrl(BASE_URL)
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.build();
}
}
Параметры, поддерживаемые WebClient.Builder можно посмотреть здесь.
Подготовка запроса с помощью Spring WebClient
WebClient поддерживает методы: get()
, post()
, put()
, patch()
, delete()
, options()
и head()
.
Также можно указать следующие параметры:
Переменные пути (
path variables
) и параметры запроса с помощью методаuri()
.Заголовки запроса с помощью метода
headers()
.Куки с помощью метода
cookies()
.
После указания параметров можно выполнить запрос с помощью retrieve()
или exchange()
. Далее мы преобразуем результат в Mono с помощью bodyToMono()
или во Flux с помощью bodyToFlux()
.
Асинхронный запрос
Давайте создадим сервис, который использует бин WebClient и создает асинхронный запрос.
@Service
@AllArgsConstructor
public class UserService {
private final WebClient webClient;
public Mono<User> getUserByIdAsync(final String id) {
return webClient
.get()
.uri(String.join("", "/users/", id))
.retrieve()
.bodyToMono(User.class);
}
}
Как вы видите, мы не сразу получаем модель User. Вместо User мы получаем Mono-обертку, с которой выполняем различные действия. Давайте подпишемся неблокирующим способом, используя subscribe()
.
userService
.getUserByIdAsync("1")
.subscribe(user -> log.info("Get user async: {}", user));
Выполнение продолжается сразу без блокировки на методе subscribe()
, даже если для получения значения будет требоваться некоторое время.
Синхронный запрос
Если вам нужен старый добрый синхронный вызов, то это легко сделать с помощью метода block()
.
public User getUserByIdSync(final String id) {
return webClient
.get()
.uri(String.join("", "/users/", id))
.retrieve()
.bodyToMono(User.class)
.block();
}
Здесь поток блокируется, пока запрос не выполнится. В этом случае мы получаем запрашиваемую модель сразу же после завершения выполнения метода.
Повторные попытки
Мы все знаем, что сетевой вызов не всегда может быть успешным. Но мы можем перестраховаться и в некоторых случаях выполнить его повторно. Для этого используется метод retryWhen()
, который принимает в качестве аргумента класс response.util.retry.Retry
.
public User getUserWithRetry(final String id) {
return webClient
.get()
.uri(String.join("", "/users/", id))
.retrieve()
.bodyToMono(User.class)
.retryWhen(Retry.fixedDelay(3, Duration.ofMillis(100)))
.block();
}
С помощью билдера можно настроить параметры и различные стратегии повтора (например, экспоненциальную). Если вам нужно повторить успешную попытку, то используйте repeatWhen()
или repeatWhenEmpty()
вместо retryWhen()
.
Обработка ошибок
В случае ошибки, когда повторная попытка не помогает, мы все еще можем контролировать ситуацию с помощью резервного варианта. Доступны следующие методы:
doOnError()
— срабатывает, когда Mono завершается с ошибкой.
onErrorResume()
— при возникновении ошибки подписывается на резервного издателя, используя функцию для выбора действия в зависимости от ошибки.
Вы можете использовать эти функции для вызова другого сервиса, выбрасывания исключения, записи в лог или выполнения любого действия в зависимости от ошибки.
public User getUserWithFallback(final String id) {
return webClient
.get()
.uri(String.join("", "/broken-url/", id))
.retrieve()
.bodyToMono(User.class)
.doOnError(error -> log.error("An error has occurred {}", error.getMessage()))
.onErrorResume(error -> Mono.just(new User()))
.block();
}
В некоторых ситуациях может быть полезно отреагировать на конкретный код ошибки. Для этого можно использовать метод onStatus()
.
public User getUserWithErrorHandling(final String id) {
return webClient
.get()
.uri(String.join("", "/broken-url/", id))
.retrieve()
.onStatus(HttpStatus::is4xxClientError,
error -> Mono.error(new RuntimeException("API not found")))
.onStatus(HttpStatus::is5xxServerError,
error -> Mono.error(new RuntimeException("Server is not responding")))
.bodyToMono(User.class)
.block();
}
Клиентские фильтры
Для перехвата и изменения запроса можно настроить фильтры через билдер WebClient .
WebClient.builder()
.baseUrl(BASE_URL)
.filter((request, next) -> next
.exchange(ClientRequest.from(request)
.header("foo", "bar")
.build()))
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.build();
Ниже приведен пример фильтра для базовой аутентификации с помощью статического фабричного метода.
WebClient.builder()
.baseUrl(BASE_URL)
.filter(basicAuthentication("user", "password")) // org.springframework.web.reactive.function.client.basicAuthentication()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.build();
Заключение
В этой статье мы узнали, как настроить WebClient и выполнять синхронные и асинхронные HTTP-запросы. Все фрагменты кода, упомянутые в статье, можно найти в GitHub-репозитории. Документацию по Spring WebClient вы можете найти здесь.
Подводя итог, мы видим, что WebClient прост в использовании и включает в себя все необходимые функции, необходимые в современном программировании.
Удачи с новым Spring WebClient!
Узнать подробнее о курсе «Разработчик на Spring Framework».
Посмотреть запись демо-урока на тему «Введение в облака, создание кластера в Mongo DB Atlas».