环境:SpringBoot 2.4.12
概述
Spring WebFlux包括一个用来执行HTTP请求的客户端。WebClient有一个基于Reactor(反应式)的功能性的、流畅的API,它支持异步逻辑的声明式组合,而不需要处理线程或并发性。它是完全非阻塞的,它支持流,并依赖于同样的编解码器,这些编解码器也用于在服务器端对请求和响应内容进行编码和解码。
WebClient需要一个Http Client库来执行请求。内置支持以下功能:
- Reactor Netty
https://github.com/reactor/reactor-netty
- Jetty Reactive HttpClient
https://github.com/jetty-project/jetty-reactive-httpclient
- Apache HttpComponents
https://hc.apache.org/index.html
- 其他可以通过ClientHttpConnector插入。
WebClient配置
创建WebClient最简单的方法是通过静态工厂方法之一:
- WebClient#create()
- WebClient#create(String baseUrl)
你也可以使用WebClient#builder提供更多的选项:
- uriBuilderFactory:定制的uriBuilderFactory用作基础URL。
- defaultUriVariables:在展开URI模板时使用的默认值。
- defaultHeader:每个请求的头文件。
- defaultCookie:每个请求的cookie。
- defaultRequest:自定义每个请求的使用者。
- filter:针对每个请求的客户端过滤器。
- exchangeStrategies: HTTP消息读取器/写入器自定义。
- clientConnector: HTTP客户端库设置。
示例1:
WebClient client = WebClient.builder()
.codecs(configurer -> ... )
.build();
一旦创建了WebClient就是不可变的。不过,你可以克隆它并建立一个修改后的副本如下:
示例2:
WebClient client1 = WebClient.builder()
.filter(filterA).filter(filterB).build();
// 创建一个副本
WebClient client2 = client1.mutate()
.filter(filterC).filter(filterD).build();
最大内存配置
为了避免应用程序内存问题,编解码器对内存中的数据缓存有限制。默认情况下,它们被设置为256KB。如果这还不够,你会得到以下错误:
org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer
修改默认的最大内存
WebClient webClient = WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.build();
Reactor Netty
网络请求Http Client。
自定义Reactor Netty设置,超时设置
HttpClient httpClient = HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000).doOnConnected(con -> {
con.addHandlerFirst(new ReadTimeoutHandler(2, TimeUnit.SECONDS)) ;
con.addHandlerLast(new WriteTimeoutHandler(10));
});
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient) ;
Builder builder = WebClient.builder().clientConnector(connector) ;
// 响应超时配置
HttpClient httpClient = HttpClient.create().responseTimeout(Duration.ofSeconds(2));
获取响应数据
WebClient client = WebClient.create("https://example.org");
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Person.class);
仅仅获取响应数据
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
默认情况下,4xx或5xx响应会导致WebClientResponseException,包括特定HTTP状态码的子类。要自定义错误响应的处理,使用onStatus处理程序如下所示:
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
Exchange操作
exchangeToMono()和exchangeToFlux()方法对于需要更多控制的更高级情况很有用,比如根据响应状态对响应进行不同的处理:
@GetMapping("/removeInvoke3")
public Mono<R> remoteInvoke3() {
return wc.get()
.uri("http://localhost:9000/users/get?id={id}", new Random().nextInt(1000000))
.exchangeToMono(clientResponse -> {
if (clientResponse.statusCode().equals(HttpStatus.OK)) {
return clientResponse.bodyToMono(Users.class);
} else {
return clientResponse.createException().flatMap(Mono::error) ;
}
})
.log()
.flatMap(user -> Mono.just(R.success(user)))
.retry(3) // 重试次数
.onErrorResume(ex -> {
return Mono.just(R.failure(ex.getMessage())) ;
});
}
请求体
请求体可以从ReactiveAdapterRegistry处理的任何异步类型进行编码,比如Mono如下例所示:
Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);
如果你有实际值,你可以使用bodyValue方法,如下例所示:
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(person)
.retrieve()
.bodyToMono(Void.class);
Form Data
要发送表单数据,可以提供MultiValueMap作为主体。注意,内容被FormHttpMessageWriter自动设置为application/x-www-form-urlencoded。下面的例子展示了如何使用MultiValueMap:
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.bodyValue(formData)
.retrieve()
.bodyToMono(Void.class);
便捷方法
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);
过滤器
你可以通过WebClient注册客户端过滤器(ExchangeFilterFunction)。生成器,以便拦截和修改请求,如下例所示:
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
完毕!!!
Spring WebFlux请求处理流程
Spring WebFlux使用函数式编程之HandlerFunction(1)
Spring WebFlux使用函数式编程之RouterFunction(2)
一文带你彻底理解Spring WebFlux的工作原理
SpringBoot WebFlux整合Spring Security进行权限认证
SpringBoot WebFlux整合MongoDB实现CRUD及分页功能
Spring WebFlux核心处理组件DispatcherHandler
Spring WebFlux入门实例并整合数据库实现基本的增删改查
Spring WebFlux使用函数式编程之Filtering Handler Functions
SpringBoot WebFlux整合R2DBC实现数据库反应式编程