SpringWebFlux-4(webflux配置、请求客户端学习笔记2021.11.10)

SpringWebFlux -4(5.3.12版学习笔记2021.11.10)

1.11. WebFlux 配置

与 Spring MVC 中的相同

WebFlux Java 配置声明使用带 Comments 的控制器或功能端点来声明处理请求所必需的组件,并且它提供了用于自定义配置的 API。这意味着您不需要了解 Java 配置创建的底层 bean。但是,如果您想了解它们,则可以在WebFluxConfigurationSupport中查看它们,或在特殊 bean 类中阅读有关它们的更多信息, 要获得配置 API 中没有的更高级的自定义设置,您可以通过高级配置模式获得对配置的完全控制。

1.11.1. 启用 WebFlux 配置

与 Spring MVC 中的相同

您可以在 Java 配置中使用@EnableWebFluxComments,如以下示例所示:

@Configuration
@EnableWebFlux
public class WebConfig {
}

前面的示例注册了许多 Spring WebFlux infrastructure beans,并适应了 classpath 上可用的依赖项(对于 JSON,XML 等)。

如果你使用spring-boot-starter-webflux配置是通过ReactiveWebServerFactoryAutoConfigurationWebFluxAutoConfiguration自动完成的,所以你不需要@EnableWebFlux,  当您在没有spring-boot情况下使用spring-webflux ,您需要在@Configuration类上添加@EnableWebFlux以从WebFluxConfigurationSupport导入 Spring WebFlux 配置

1.11.2. WebFlux 配置 API

与 Spring MVC 中的相同

在 Java 配置中,您可以实现WebFluxConfigurer接口, 配置WebFlux 配置, 如以下示例所示:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
    // Implement configuration methods...
}

1.11.3. 转换,格式化 (配置)

与 Spring MVC 中的相同

默认情况下,将安装NumberDate类型的格式化程序,包括对@NumberFormat@DateTimeFormat注解的支持。如果 Classpath 中存在 Joda-Time,则还将安装对 Joda-Time 格式库的完全支持。

下面的示例演示如何注册自定义格式器和转换器:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }
}

有关何时使用FormatterRegistrar实现的更多信息,请参见FormatterRegistrar SPIFormattingConversionServiceFactoryBean

1.11.4. Validation (验证器配置)

与 Spring MVC 中的相同

默认情况下,如果Bean Validation存在于 Classpath(例如,Hibernate Validator)上,则LocalValidatorFactoryBean被注册为全局validator,以便与@Controller方法参数上的@ValidValidated一起使用。

在 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 MVC 中的相同

您可以配置 Spring WebFlux 如何根据请求确定@Controller实例的请求媒体类型。默认情况下,仅选中AcceptHeaders,但您也可以启用基于查询参数的策略。

以下示例显示如何自定义请求的 Content Type 解析:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
    @Override
    public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
        // ...
    }
}

1.11.6. HTTP 消息编解码器

与 Spring MVC 中的相同

以下示例显示如何自定义如何读取和写入请求和响应正文:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // ...
    }
}

ServerCodecConfigurer提供了一组默认的读取器和写入器。您可以使用它来添加更多读取器和写入器,自定义默认读取器或完全替换默认读取器。

对于 Jackson JSON 和 XML,请考虑使用Jackson2ObjectMapperBuilder,它使用以下属性自定义 Jackson 的默认属性:

如果在 Classpath 中检测到以下知名模块,它还将自动注册以下知名模块:

1.11.7. 视图解析器配置

与 Spring MVC 中的相同

以下示例显示如何配置视图分辨率:

@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. 静态资源配置

与 Spring MVC 中的相同

此选项提供了一种方便的方式来从基于Resource的位置列表中提供静态资源。

在下一个示例中,给定一个以/resources开头的请求,相对路径用于在 Classpath 上查找和提供与/static相关的静态资源。资源的有效期为一年,以确保最大程度地利用浏览器缓存并减少浏览器发出的 HTTP 请求。 Last-ModifiedHeaders 也会被评估,如果存在,则返回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. 路径匹配配置

与 Spring MVC 中的相同

您可以自定义与路径匹配有关的选项。有关各个选项的详细信息,请参见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. 高级配置模式

与 Spring MVC 中的相同

@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 配置声明了一个WebSocketHandlerAdapterbean,它为调用 WebSocket 处理程序提供支持。这意味着为了处理 WebSocket 握手请求,剩下要做的就是通过 将 a 映射WebSocketHandler到 URL SimpleUrlHandlerMapping

在某些情况下,可能需要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

与 Spring MVC 中的相同

需要 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。
  • defaultRequestConsumer自定义每个请求。
  • 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 应用程序),则可以声明ReactorResourceFactoryglobalResources=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创建自己的资源(ExecutorByteBufferPoolScheduler),这些资源将保持活动状态,直到进程退出或调用stop()为止。

您可以在 JettyClient 端(和服务器)的多个实例之间共享资源,并pass 语句类型为JettyResourceFactory的 Spring 托管 bean 来确保在关闭 Spring ApplicationContext时关闭资源,如以下示例所示:

@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.BadRequestWebClientResponseException.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的任何bodytoEntity方法,以确保释放资源并避免 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,或之后执行,例如 via concatMap,那么您需要使用 Reactor Context

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();

以上只是一个例子。有许多其他模式和操作符可以组合一个反应式管道,这些管道可以进行许多远程调用,可能是一些嵌套的、相互依赖的,直到最后都不会阻塞。

使用FluxMono,您永远不必在 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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懵懵懂懂程序员

如果节省了你的时间, 请鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值