Открыть список
Как стать автором
Обновить
489,5
Рейтинг
OTUS
Цифровые навыки от ведущих экспертов

Шпаргалка по Spring Boot WebClient

Блог компании OTUSПрограммированиеJava
Перевод
Автор оригинала: Stanislav Vain

В преддверии старта курса «Разработчик на 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 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».

Теги:javaspringwebclientspring bootasynchronousreactive programming
Хабы: Блог компании OTUS Программирование Java
Всего голосов 9: ↑9 и ↓0 +9
Просмотры4.4K

Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Похожие публикации

Лучшие публикации за сутки

Информация

Дата основания
Местоположение
Россия
Сайт
otus.ru
Численность
51–100 человек
Дата регистрации
Представитель
OTUS

Блог на Хабре