Привет всем, я Букай Чен~
В Spring Framework 5.0 и более поздних версиях RestTemplate устарел в пользу более нового WebClient. Это означает, что, хотя RestTemplate все еще доступен, разработчикам Spring рекомендуется перейти на WebClient для новых проектов.
Есть несколько причин, почему WebClient лучше RestTemplate:
Ключевой момент: даже если весна будет обновлена web В версии 6.0.0 запрос тайм-аут не может быть установлен в существующем HttpRequestFactory, от которого отказались. RestTemplate один из важнейших факторов.
Установка тайм-аута запроса не даст никакого эффекта
В целом, хотя RestTemplate все еще может подойти для некоторых случаев использования, WebClient предлагает несколько преимуществ, которые делают его лучшим выбором для современных приложений Spring.
Давайте посмотрим, как использовать WebClient в приложении SpringBoot 3.
import io.netty.channel.ChannelOption;
import io.netty.channel.ConnectTimeoutException;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.TimeoutException;
import jakarta.annotation.PostConstruct;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientRequestException;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
HttpClient httpClient =
HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
.responseTimeout(Duration.ofMillis(requestTimeout))
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(readTimeout)));
WebClient client =
WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();
Если вы хотите придерживаться старого метода отправки HTTP-запроса и ожидания ответа, вы также можете использовать WebClient для достижения той же функциональности, как показано ниже:
public String postSynchronously(String url, String requestBody) {
LOG.info("Going to hit API - URL {} Body {}", url, requestBody);
String response = "";
try {
response =
client
.method(HttpMethod.POST)
.uri(url)
.accept(MediaType.ALL)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.retrieve()
.bodyToMono(String.class)
.block();
} catch (Exception ex) {
LOG.error("Error while calling API ", ex);
throw new RunTimeException("XYZ service api error: " + ex.getMessage());
} finally {
LOG.info("API Response {}", response);
}
return response;
}
Block() используется для синхронного ожидания ответа. Это может подойти не для всех ситуаций. Возможно, вы захотите использовать асинхронную функцию subscribe() и асинхронную обработку ответа.
Иногда мы не хотим ждать ответа, а хотим обработать его асинхронно. Это можно сделать следующим образом:
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public static Mono<String> makePostRequestAsync(String url, String postData) {
WebClient webClient = WebClient.builder().build();
return webClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("data", postData))
.retrieve()
.bodyToMono(String.class);
}
Чтобы использовать эту функцию, просто передайте URL-адрес, на который вы хотите отправить запрос POST, и данные для отправки в виде строки в кодировке URL-адреса в теле запроса. Подпишитесь на аккаунт Gongzhong: Колонка технологий Ма Юаня, ответьте на ключевые слова: 1111, чтобы получить внутреннее руководство по настройке производительности Java Alibaba! Эта функция вернет ответ от сервера или сообщение об ошибке, если запрос по какой-либо причине не выполнен.
Обратите внимание, что в этом примере WebClient создан с конфигурацией по умолчанию. Возможно, вам придется настроить его по-разному в зависимости от различных требований. Также обратите внимание, что функция Block() используется для синхронного ожидания ответа, что может подходить не для всех ситуаций. Возможно, вы захотите использовать асинхронную функцию subscribe() и обработать ответ.
Чтобы использовать ответ, вы можете подписаться на Mono и обрабатывать ответ асинхронно. Вот пример:
makePostRequestAsync( "https://example.com/api" , "param1=value1¶m2=value2" )
.subscribe(response -> {
// Обработка ответа
System.out.println ( response );
}, error -> {
/ / обработка ошибок
System.err.println ( error .getMessage ());
}
);
subscribe() используется для асинхронной обработки ответов. В качестве параметров subscribe() можно указать два лямбда-выражения. Если запрос успешен и в качестве параметра получен ответ, выполняется первое лямбда-выражение; если запрос не выполнен и в качестве параметра получена ошибка, выполняется второе лямбда-выражение.
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public static Mono<String> makePostRequestAsync(String url, String postData) {
WebClient webClient = WebClient.builder()
.baseUrl(url)
.build();
return webClient.post()
.uri("/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("data", postData))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Client error")))
.onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Server error")))
.bodyToMono(String.class);
}
В этом примере метод onStatus() вызывается дважды: один раз для ошибок клиента 4xx и один раз для ошибок сервера 5xx. Каждый вызов onStatus() принимает два параметра:
Если код состояния соответствует условию, Mono выдает соответствующий код состояния, и цепочка Mono завершается с ошибкой. В этом примере Mono выдаст сообщение об ошибке RuntimeException, указывающее, является ли ошибка ошибкой клиента или сервера.
Чтобы принять меры в случае ошибки в методе subscribe() Mono, вы можете добавить еще одно лямбда-выражение после лямбда-выражения, которое обрабатывает ответ в функции подписки. Если при обработке Monnumber возникает ошибка, выполняется второе лямбда-выражение.
Вот обновленный пример того, как использовать функцию makePostRequestAsync и обрабатывать ошибки в методе подписки:
makePostRequestAsync("https://example.com/api", "param1=value1¶m2=value2")
.subscribe(response -> {
// handle the response
System.out.println(response);
}, error -> {
// handle the error
System.err.println("An error occurred: " + error.getMessage());
if (error instanceof WebClientResponseException) {
WebClientResponseException webClientResponseException = (WebClientResponseException) error;
int statusCode = webClientResponseException.getStatusCode().value();
String statusText = webClientResponseException.getStatusText();
System.err.println("Error status code: " + statusCode);
System.err.println("Error status text: " + statusText);
}
});
Второе лямбда-выражение в методе подписки проверяет, является ли ошибка экземпляром исключения WebClientResponseException, которое представляет собой особый тип исключения, создаваемого WebClient, когда сервер отвечает ошибкой. Если это экземпляр WebClientResponseException, код извлечет код состояния и текст состояния из исключения и зарегистрирует их.
Вы также можете добавить дополнительную логику обработки ошибок в это лямбда-выражение на основе конкретной возникшей ошибки. Например, вы можете повторить запрос, вернуться к значениям по умолчанию или определенным образом зарегистрировать ошибки.
responseMono.subscribe(
response -> {
// handle the response
LOG.info("SUCCESS API Response {}", response);
},
error -> {
// handle the error
LOG.error("An error occurred: {}", error.getMessage());
LOG.error("error class: {}", error.getClass());
// Errors / Exceptions from Server
if (error instanceof WebClientResponseException) {
WebClientResponseException webClientResponseException =
(WebClientResponseException) error;
int statusCode = webClientResponseException.getStatusCode().value();
String statusText = webClientResponseException.getStatusText();
LOG.info("Error status code: {}", statusCode);
LOG.info("Error status text: {}", statusText);
if (statusCode >= 400 && statusCode < 500) {
LOG.info(
"Error Response body {}", webClientResponseException.getResponseBodyAsString());
}
Throwable cause = webClientResponseException.getCause();
LOG.error("webClientResponseException");
if (null != cause) {
LOG.info("Cause {}", cause.getClass());
if (cause instanceof ReadTimeoutException) {
LOG.error("ReadTimeout Exception");
}
if (cause instanceof TimeoutException) {
LOG.error("Timeout Exception");
}
}
}
// Client errors i.e. Timeouts etc -
if (error instanceof WebClientRequestException) {
LOG.error("webClientRequestException");
WebClientRequestException webClientRequestException =
(WebClientRequestException) error;
Throwable cause = webClientRequestException.getCause();
if (null != cause) {
LOG.info("Cause {}", cause.getClass());
if (cause instanceof ReadTimeoutException) {
LOG.error("ReadTimeout Exception");
}
if (cause instanceof ConnectTimeoutException) {
LOG.error("Connect Timeout Exception");
}
}
}
});
Мы можем установить тайм-аут в каждом запросе следующим образом:
return webClient
.method(this.httpMethod)
.uri(this.uri)
.headers(httpHeaders -> httpHeaders.addAll(additionalHeaders))
.bodyValue(this.requestEntity)
.retrieve()
.bodyToMono(responseType)
.timeout(Duration.ofMillis(readTimeout)) // request timeout for this request
.block();
Однако мы не можем существовать устанавливать тайм-аут соединения в каждом запросе, это WebClient свойства можно задать только один раз. При необходимости мы всегда можем создать новое соединение с новым значением тайм-аута. Web Экземпляр клиента.
Различия между подключением тайм-аута, чтением тайм-аута и запросом тайм-аута заключаются в следующем:
Поскольку RestTemplace устарел, разработчикам следует начать использовать WebClient для вызовов REST, неблокирующие вызовы ввода-вывода определенно улучшат производительность приложения. Он не только предоставляет множество других интересных функций, таких как улучшенная обработка ошибок и поддержка потоков, но его также можно использовать в режиме блокировки для имитации поведения RestTemplate, если это необходимо.