SpringWebFlux
-4(5.3.12版学习笔记2021.11.10)
1.11. WebFlux 配置
WebFlux Java 配置声明使用带 Comments 的控制器或功能端点来声明处理请求所必需的组件,并且它提供了用于自定义配置的 API。这意味着您不需要了解 Java 配置创建的底层 bean。但是,如果您想了解它们,则可以在
WebFluxConfigurationSupport
中查看它们,或在特殊 bean 类中阅读有关它们的更多信息, 要获得配置 API 中没有的更高级的自定义设置,您可以通过高级配置模式获得对配置的完全控制。
1.11.1. 启用 WebFlux 配置
您可以在 Java 配置中使用
@EnableWebFlux
Comments,如以下示例所示:
@Configuration
@EnableWebFlux
public class WebConfig {
}
前面的示例注册了许多 Spring WebFlux infrastructure beans,并适应了 classpath 上可用的依赖项(对于 JSON,XML 等)。
如果你使用
spring-boot-starter-webflux
配置是通过ReactiveWebServerFactoryAutoConfiguration
和WebFluxAutoConfiguration
自动完成的,所以你不需要@EnableWebFlux
, 当您在没有spring-boot
情况下使用spring-webflux
,您需要在@Configuration
类上添加@EnableWebFlux
以从WebFluxConfigurationSupport
导入 Spring WebFlux 配置
1.11.2. WebFlux 配置 API
在 Java 配置中,您可以实现
WebFluxConfigurer
接口, 配置WebFlux 配置, 如以下示例所示:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
// Implement configuration methods...
}
1.11.3. 转换,格式化 (配置)
默认情况下,将安装
Number
和Date
类型的格式化程序,包括对@NumberFormat
和@DateTimeFormat
注解的支持。如果 Classpath 中存在 Joda-Time,则还将安装对 Joda-Time 格式库的完全支持。下面的示例演示如何注册自定义格式器和转换器:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
有关何时使用
FormatterRegistrar
实现的更多信息,请参见FormatterRegistrar SPI和FormattingConversionServiceFactoryBean
。
1.11.4. Validation (验证器配置)
默认情况下,如果Bean Validation存在于 Classpath(例如,Hibernate Validator)上,则
LocalValidatorFactoryBean
被注册为全局validator,以便与@Controller
方法参数上的@Valid
和Validated
一起使用。在 Java 配置中,您可以自定义全局
Validator
实例,如以下示例所示:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public Validator getValidator(); {
// ...
}
}
请注意,您还可以在本地注册
Validator
实现,如以下示例所示:
@Controller
public class MyController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());
}
}
如果需要在某处注入
LocalValidatorFactoryBean
,请创建一个 bean 并用@Primary
进行标记,以避免与 MVC 配置中声明的那个冲突。
1.11.5. Content Type 解析器
您可以配置 Spring WebFlux 如何根据请求确定
@Controller
实例的请求媒体类型。默认情况下,仅选中Accept
Headers,但您也可以启用基于查询参数的策略。以下示例显示如何自定义请求的 Content Type 解析:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
// ...
}
}
1.11.6. HTTP 消息编解码器
以下示例显示如何自定义如何读取和写入请求和响应正文:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// ...
}
}
ServerCodecConfigurer
提供了一组默认的读取器和写入器。您可以使用它来添加更多读取器和写入器,自定义默认读取器或完全替换默认读取器。对于 Jackson JSON 和 XML,请考虑使用Jackson2ObjectMapperBuilder,它使用以下属性自定义 Jackson 的默认属性:
如果在 Classpath 中检测到以下知名模块,它还将自动注册以下知名模块:
- jackson-datatype-jdk7:支持
java.nio.file.Path
之类的 Java 7 类型。 - jackson-datatype-joda:支持 Joda-Time 类型。
- jackson-datatype-jsr310:支持 Java 8 日期和时间 API 类型。
- jackson-datatype-jdk8:支持其他 Java 8 类型,例如
Optional
。
1.11.7. 视图解析器配置
以下示例显示如何配置视图分辨率:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// ...
}
}
ViewResolverRegistry
具有用于与 Spring Framework 集成的视图技术的快捷方式。以下示例使用 FreeMarker(这也需要配置基础 FreeMarker 视图技术):
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
}
// Configure Freemarker...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates");
return configurer;
}
}
您还可以插入任何
ViewResolver
实现,如以下示例所示:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
ViewResolver resolver = ... ;
registry.viewResolver(resolver);
}
}
要支持Content Negotiation并通过视图分辨率(除 HTML 之外)呈现其他格式,您可以基于
HttpMessageWriterView
实现配置一个或多个默认视图,该实现接受spring-web
中的任何可用Codecs。以下示例显示了如何执行此操作:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
registry.defaultViews(new HttpMessageWriterView(encoder));
}
// ...
}
有关与 Spring WebFlux 集成的视图技术的更多信息,请参见View Technologies。
1.11.8. 静态资源配置
此选项提供了一种方便的方式来从基于Resource的位置列表中提供静态资源。
在下一个示例中,给定一个以
/resources
开头的请求,相对路径用于在 Classpath 上查找和提供与/static
相关的静态资源。资源的有效期为一年,以确保最大程度地利用浏览器缓存并减少浏览器发出的 HTTP 请求。Last-Modified
Headers 也会被评估,如果存在,则返回304
状态码。以下列表显示了示例:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public", "classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}
资源处理程序还支持ResourceResolver个实现和ResourceTransformer个实现的链,可用于创建工具链以使用优化的资源。
您可以将
VersionResourceResolver
用于基于资源,固定应用程序版本或其他信息计算出的 MD5 哈希的版本化资源 URL。ContentVersionStrategy
(MD5 哈希)是一个不错的选择,但有一些值得注意的 exception(例如与模块加载器一起使用的 JavaScript 资源)。以下示例显示了如何在 Java 配置中使用
VersionResourceResolver
:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
您可以使用
ResourceUrlProvider
重写 URL 并应用完整的解析器和转换器链(例如,插入版本)。 WebFlux 配置提供ResourceUrlProvider
,以便可以将其注入其他对象。与 Spring MVC 不同,目前,在 WebFlux 中,由于没有视图技术可以利用解析器和转换器的无阻塞链,因此无法透明地重写静态资源 URL。当仅提供本地资源时,解决方法是直接使用
ResourceUrlProvider
(例如,通过自定义元素)并进行阻止。请注意,当同时使用
EncodedResourceResolver
(例如,Gzip,Brotli 编码)和VersionedResourceResolver
时,必须按该 Sequences 注册它们,以确保始终基于未编码文件可靠地计算基于内容的版本。WebJars也受
WebJarsResourceResolver
支持,并且当org.webjars:webjars-locator
存在于 Classpath 中时会自动注册。解析器可以重写 URL 以包括 jar 的版本,并且还可以与没有版本的传入 URL 匹配(例如/jquery/jquery.min.js
到/jquery/1.2.0/jquery.min.js
)。
1.11.9. 路径匹配配置
您可以自定义与路径匹配有关的选项。有关各个选项的详细信息,请参见PathMatchConfigurer javadoc。以下示例显示了如何使用
PathMatchConfigurer
:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseCaseSensitiveMatch(true)
.setUseTrailingSlashMatch(false)
.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
}
Spring WebFlux 依赖于名为
RequestPath
的请求路径的解析表示形式,以访问已解码的路径段值,并删除了分号内容(即路径或矩阵变量)。这意味着,与 Spring MVC 不同,您无需指示是否解码请求路径,也无需指示是否出于路径匹配目的而删除分号内容。Spring WebFlux 也不支持后缀模式匹配,这与 Spring MVC 不同,在 Spring MVC 中,我们也recommend不再依赖它。
1.11.10. 高级配置模式
@EnableWebFlux
导入DelegatingWebFluxConfiguration
:
- 为 WebFlux 应用程序提供默认的 Spring 配置
- 检测并委托
WebFluxConfigurer
个实现来自定义该配置。
对于高级模式,可以删除
@EnableWebFlux
并直接从DelegatingWebFluxConfiguration
扩展,而不是实现WebFluxConfigurer
,如以下示例所示:
@Configuration
public class WebConfig extends DelegatingWebFluxConfiguration {
// ...
}
您可以将现有方法保留在
WebConfig
中,但是现在您还可以覆盖 Base Class 中的 bean 声明,并且在 Classpath 上仍然具有任意数量的其他WebMvcConfigurer
实现。
1.11.11。WebSocket (网络套接字服务)
WebFlux Java 配置声明了一个
WebSocketHandlerAdapter
bean,它为调用 WebSocket 处理程序提供支持。这意味着为了处理 WebSocket 握手请求,剩下要做的就是通过 将 a 映射WebSocketHandler
到 URLSimpleUrlHandlerMapping
。在某些情况下,可能需要
WebSocketHandlerAdapter
使用提供的WebSocketService
服务创建bean,该服务允许配置 WebSocket 服务器属性。例如:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public WebSocketService getWebSocketService() {
TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
strategy.setMaxSessionIdleTimeout(0L);
return new HandshakeWebSocketService(strategy);
}
}
1.12 HTTP/2
需要 Servlet 4 容器支持 HTTP/2,并且 Spring Framework 5 与 Servlet API 4 兼容。从编程模型的角度来看,应用程序不需要做任何特定的事情。但是,有一些与服务器配置有关的注意事项。有关更多详细信息,请参见HTTP/2 Wiki 页面。
当前,Spring WebFlux 不支持 Netty 的 HTTP/2.也没有支持以编程方式将资源推送到 Client 端。
2.0 WebClient (web客户端)
Spring WebFlux 包括一个用于 HTTP 请求的 Reactive 非阻塞
WebClient
。Client 端具有功能性,Fluent 的 API,具有用于声明式组合的反应式类型,请参见Reactive Libraries。 WebFluxClient 端和服务器依靠相同的非阻塞codecs对请求和响应内容进行编码和解码。在内部
WebClient
委托给 HTTPClient 端库。默认情况下,它使用Reactor Netty,内置了对 Jetty reactive HtpClient的支持,其他的可以通过ClientHttpConnector
插入。
2.1. Configuration
创建
WebClient
的最简单方法是通过静态工厂方法之一:
WebClient.create()
WebClient.create(String baseUrl)
上面的方法使用具有默认设置的 Reactor Netty
HttpClient
,并期望io.projectreactor.netty:reactor-netty
位于 Classpath 中。您还可以将
WebClient.builder()
与其他选项一起使用:
uriBuilderFactory
:自定义UriBuilderFactory
用作基本 URL。defaultHeader
:每个请求的标题。defaultCookie
:每个请求的 cookie。defaultRequest
:Consumer
自定义每个请求。filter
:针对每个请求的 Client 端过滤器。exchangeStrategies
:HTTP 消息读取器/写入器定制。clientConnector
:HTTPClient 端库设置。
以下示例配置HTTP codecs:
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
// ...
})
.build();
WebClient client = WebClient.builder()
.exchangeStrategies(strategies)
.build();
构建后,
WebClient
实例是不可变的。但是,您可以克隆mutate()
它并构建修改后的副本,而不会影响原始实例,如以下示例所示:
WebClient client1 = WebClient.builder()
.filter(filterA).filter(filterB).build();
WebClient client2 = client1.mutate()
.filter(filterC).filter(filterD).build();
// client1 has filterA, filterB
// client2 has filterA, filterB, filterC, filterD
2.1.0. 最大内存大小
编解码器对在内存中缓冲数据有限制以避免应用程序内存问题。默认情况下,这些设置为 256KB。如果这还不够,您将收到以下错误:
org.springframework.core.io.buffer.DataBufferLimitException:超出了要缓冲的最大字节数的限制
// 要更改默认编解码器的限制,请使用以下命令:
WebClient webClient = WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.build();
2.1.1. 反应堆净值
要自定义 Reactor Netty 设置,只需提供一个预先配置的
HttpClient
:
HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Resources
默认情况下,
HttpClient
参与reactor.netty.http.HttpResources
拥有的全局 Reactor Netty 资源,包括事件循环线程和连接池。这是推荐的模式,因为固定的共享资源是事件循环并发的首选。在这种模式下,全局资源将保持活动状态,直到进程退出。如果服务器为该进程计时,则通常无需显式关闭。但是,如果服务器可以启动或停止进程内(例如,作为 WAR 部署的 Spring MVC 应用程序),则可以声明
ReactorResourceFactory
和globalResources=true
(默认值)类型的 Spring 托管 bean,以确保 Reactor Netty 全局 SpringApplicationContext
关闭时,资源将关闭,如以下示例所示:
@Bean
public ReactorResourceFactory reactorResourceFactory() {
return new ReactorResourceFactory();
}
您也可以选择不参与全局 Reactor Netty 资源。但是,在这种模式下,确保所有 Reactor NettyClient 端和服务器实例使用共享资源是您的重担,如以下示例所示:
@Bean
public ReactorResourceFactory resourceFactory() {
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setGlobalResources(false); (1 = 创建独立于全局资源的资源)
return factory;
}
@Bean
public WebClient webClient() {
Function<HttpClient, HttpClient> mapper = client -> {
// Further customizations...
};
ClientHttpConnector connector =
new ReactorClientHttpConnector(resourceFactory(), mapper); (2 = 将ReactorClientHttpConnector构造函数与资源工厂一起使用)
return WebClient.builder().clientConnector(connector).build(); (3 = 将连接器插入WebClient.Builder)
}
Timeouts (超时)
import io.netty.channel.ChannelOption;
// 要配置连接超时:
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
// 要配置读取和/或写入超时值:
HttpClient httpClient = HttpClient.create()
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10)));
// 为所有请求配置响应超时:
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(2));
// Create WebClient...
// 要为特定请求配置响应超时:
WebClient.create().get()
.uri("https://example.org/path")
.httpRequest(httpRequest -> {
HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
reactorRequest.responseTimeout(Duration.ofSeconds(2));
})
.retrieve()
.bodyToMono(String.class);
2.1.2. Jetty
以下示例显示了如何自定义 Jetty HttpClient
设置:
HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);
ClientHttpConnector connector = new JettyClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder().clientConnector(connector).build();
默认情况下,
HttpClient
创建自己的资源(Executor
,ByteBufferPool
,Scheduler
),这些资源将保持活动状态,直到进程退出或调用stop()
为止。您可以在 JettyClient 端(和服务器)的多个实例之间共享资源,并pass 语句类型为
JettyResourceFactory
的 Spring 托管 bean 来确保在关闭 SpringApplicationContext
时关闭资源,如以下示例所示:
@Bean
public JettyResourceFactory resourceFactory() {
return new JettyResourceFactory();
}
@Bean
public WebClient webClient() {
Consumer<HttpClient> customizer = client -> {
// Further customizations...
};
ClientHttpConnector connector =
new JettyClientHttpConnector(resourceFactory(), customizer); (1 = 将JettyClientHttpConnector构造函数与资源工厂一起使用)
return WebClient.builder().clientConnector(connector).build(); (2 = 将连接器插入WebClient.Builder)
}
2.2. retrieve()
retrieve()
方法是获取响应正文并将其解码的最简单方法。以下示例显示了如何执行此操作:
WebClient client = WebClient.create("http://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class); // .toEntity(Person.class);
您还可以从响应中解码出一个对象流,如以下示例所示:
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
默认情况下,带有 4xx 或 5xx 状态代码的响应会导致
WebClientResponseException
或其 HTTP 状态特定的子类之一,例如WebClientResponseException.BadRequest
,WebClientResponseException.NotFound
等。您还可以使用onStatus
方法来自定义产生的异常,如以下示例所示:
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxServerError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
使用
onStatus
时,如果期望响应包含内容,则onStatus
回调应使用它。否则,内容将自动耗尽以确保释放资源。
2.3. exchange()
exchange()
方法比retrieve
方法提供更多的控制权。以下示例与retrieve()
等效,但也提供对ClientResponse
的访问:
Mono<Object> entityMono = client.get()
.uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Person.class);
}
else if (response.statusCode().is4xxClientError()) {
// Suppress error status code
return response.bodyToMono(ErrorContainer.class);
}
else {
// Turn to error
return response.createException().flatMap(Mono::error);
}
});
// 另一种
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
在此级别,您还可以创建完整的
ResponseEntity
:
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(Person.class));
请注意(与
retrieve()
不同),对于exchange()
,没有 4xx 和 5xx 响应的自动错误 signal。您必须检查状态码并决定如何进行。使用
exchange()
时,必须始终使用ClientResponse
的任何body
或toEntity
方法,以确保释放资源并避免 HTTP 连接池的潜在问题。如果不需要响应内容,则可以使用bodyToMono(Void.class)
。但是,如果响应中确实包含内容,则连接将关闭并且不会放回池中。
2.4. 请求正文
可以从
Object
编码请求主体,如以下示例所示:
Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);
您还可以对对象流进行编码,如以下示例所示:
Flux<Person> personFlux = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(personFlux, Person.class)
.retrieve()
.bodyToMono(Void.class);
或者,如果您具有实际值,则可以使用
syncBody
快捷方式,如以下示例所示:
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(person)
.retrieve()
.bodyToMono(Void.class);
2.4.1. 表单数据
要发送表单数据,您可以提供
MultiValueMap<String, String>
作为正文。请注意,内容会由FormHttpMessageWriter
自动设置为application/x-www-form-urlencoded
。以下示例显示了如何使用MultiValueMap<String, String>
:
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.bodyValue(formData)
.retrieve()
.bodyToMono(Void.class);
您还可以使用
BodyInserters
在线提供表单数据,如以下示例所示:
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);
2.4.2. Multipart 数据
要发送 Multipart 数据,您需要提供一个
MultiValueMap<String, ?>
,其值要么是代表 Component 内容的Object
实例,要么是代表 Component 内容和头的HttpEntity
实例。MultipartBodyBuilder
提供了方便的 API 以准备 Multipart 请求。以下示例显示了如何创建MultiValueMap<String, ?>
:
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart1", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
builder.part("myPart", part); // Part from a server request
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
在大多数情况下,您不必为每个部分指定
Content-Type
。Content Type 是根据要序列化的HttpMessageWriter
自动确定的,对于Resource
则根据文件 extensions 自动确定。如有必要,您可以通过重载的生成器part
方法之一显式提供MediaType
供每个 Component 使用。准备好
MultiValueMap
之后,将其传递给WebClient
的最简单方法是通过syncBody
方法,如以下示例所示:
MultipartBodyBuilder builder = ...;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(builder.build())
.retrieve()
.bodyToMono(Void.class);
如果
MultiValueMap
包含至少一个非String
值,该值也可以表示常规表单数据(即application/x-www-form-urlencoded
),则无需将Content-Type
设置为multipart/form-data
。使用MultipartBodyBuilder
时,总是这样,以确保HttpEntity
包装器。作为
MultipartBodyBuilder
的替代方法,您还可以通过内置的BodyInserters
提供内联样式的 Multipart 内容,如以下示例所示:
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromMultipartData("fieldPart", "value").with("filePart", resource))
.retrieve()
.bodyToMono(Void.class);
2.5. Client 端过滤器
您可以通过
WebClient.Builder
注册 Client 端过滤器(ExchangeFilterFunction
),以拦截和修改请求,如以下示例所示:
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
这可以用于跨领域的关注,例如身份验证。以下示例使用过滤器通过静态工厂方法进行基本身份验证:
// static import of ExchangeFilterFunctions.basicAuthentication
WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "password"))
.build();
过滤器全局应用于每个请求。要更改特定请求的过滤器行为,可以将请求属性添加到
ClientRequest
,然后链中的所有过滤器都可以访问该属性,如以下示例所示:
WebClient client = WebClient.builder()
.filter((request, next) -> {
Optional<Object> usr = request.attribute("myAttribute");
// ...
})
.build();
client.get().uri("http://example.org/")
.attribute("myAttribute", "...")
.retrieve()
.bodyToMono(Void.class);
}
您也可以复制现有的
WebClient
,插入新的过滤器或删除已注册的过滤器。以下示例在索引 0 处插入一个基本身份验证过滤器:
// static import of ExchangeFilterFunctions.basicAuthentication
WebClient client = webClient.mutate()
.filters(filterList -> {
filterList.add(0, basicAuthentication("user", "password"));
})
.build();
2.6. 语境
属性提供了一种向过滤器链传递信息的便捷方式,但它们只影响当前请求。如果您想传递传播到其他嵌套请求的信息,例如 via
flatMap
,或之后执行,例如 viaconcatMap
,那么您需要使用 ReactorContext
。
Context
为了适用于所有操作,需要在反应链的末尾填充反应器。例如:
WebClient client = WebClient.builder()
.filter((request, next) ->
Mono.deferContextual(contextView -> {
String value = contextView.get("foo");
// ...
}))
.build();
client.get().uri("https://example.org/")
.retrieve()
.bodyToMono(String.class)
.flatMap(body -> {
// perform nested request (context propagates automatically)...
})
.contextWrite(context -> context.put("foo", ...));
2.7. 同步使用
WebClient
可以通过在最后阻塞结果以同步方式使用:
Person person = client.get().uri("/person/{id}", i).retrieve()
.bodyToMono(Person.class)
.block();
List<Person> persons = client.get().uri("/persons").retrieve()
.bodyToFlux(Person.class)
.collectList()
.block();
但是,如果需要进行多次调用,避免单独阻塞每个响应,而是等待组合结果更有效:
Mono<Person> personMono = client.get().uri("/person/{id}", personId)
.retrieve().bodyToMono(Person.class);
Mono<List<Hobby>> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId)
.retrieve().bodyToFlux(Hobby.class).collectList();
Map<String, Object> data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> {
Map<String, String> map = new LinkedHashMap<>();
map.put("person", person);
map.put("hobbies", hobbies);
return map;
})
.block();
以上只是一个例子。有许多其他模式和操作符可以组合一个反应式管道,这些管道可以进行许多远程调用,可能是一些嵌套的、相互依赖的,直到最后都不会阻塞。
使用
Flux
或Mono
,您永远不必在 Spring MVC 或 Spring WebFlux 控制器中阻塞。简单地从控制器方法返回结果反应类型。相同的原则适用于 Kotlin Coroutines 和 Spring WebFlux,只需Flow
在控制器方法中使用挂起函数或返回。
2.8. Testing
要测试使用WebClient
的代码,可以使用模拟 Web 服务器,例如OkHttp MockWebServer。要查看其用法示例,请查看 Spring Framework 测试套件中的WebClientIntegrationTests或 OkHttp 存储库中的static-server示例。
扩展
WebClient
使用示例
@Test
void contextLoads() {
// 普通get请求
Map block = WebClient.create()
.get()
.uri("localhost/test/getData?name=asffasf")
.exchangeToMono(clientResponse -> clientResponse.bodyToMono(Map.class))
.onErrorResume(throwable -> {
throwable.printStackTrace();
return Mono.empty();
})
.block();
System.out.println(block);
// 普通表单post
Map<String,String> params = new HashMap<>();
params.put("name","志豪");
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.doOnConnected((el) ->
el.addHandler(new ReadTimeoutHandler(10))
.addHandler(new WriteTimeoutHandler(10))
)
.responseTimeout(Duration.ofSeconds(30));
ResponseEntity<Map> block2 = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient)) // 连接配置
.build()
.post()
.uri("localhost/test/addData")
.bodyValue(params)
.exchangeToMono((el) -> el.toEntity(Map.class))
.block();
System.out.println("表单请求结果:"+block2.getBody());
// json请求
Map<String,String> map = new HashMap<>();
params.put("age","18");
params.put("name","志豪");
Map<String,String> block3 = WebClient.create()
.post()
.uri("localhost/test/addJsonData")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(map)
.exchangeToMono((el) -> el.bodyToMono(Map.class))
.block();
System.out.println("json请求结果: "+block3);
}
1