0
点赞
收藏
分享

微信扫一扫

构建反应式堆栈 Web 应用程序的支持(二)

构建反应式堆栈 Web 应用程序的支持(二)_处理程序

1.5. 功能端点

网络MVC

Spring WebFlux 包括 WebFlux.fn,这是一个轻量级的函数式编程模型,其中函数式 用于路由和处理请求,协定旨在实现不可变性。 它是基于注释的编程模型的替代方法,但以其他方式运行 相同的反应核心基础。

1.5.1. 概述

网络MVC

在 WebFlux.fn 中,HTTP 请求由 a: 一个函数处理,该函数获取并返回延迟(即)。 请求和响应对象都具有提供 JDK 8 友好的不可变协定 访问 HTTP 请求并 response.is 等效于 基于注释的编程模型。​​HandlerFunction​​​​ServerRequest​​​​ServerResponse​​​​Mono<ServerResponse>​​​​HandlerFunction​​​​@RequestMapping​

传入的请求被路由到具有以下特征的处理程序函数: Takesand 返回一个延迟(即)。 当路由器函数匹配时,返回处理程序函数;否则,空 Mono.is 相当于 aannotation,但带有主要 不同之处在于,路由器功能不仅提供数据,还提供行为。​​RouterFunction​​​​ServerRequest​​​​HandlerFunction​​​​Mono<HandlerFunction>​​​​RouterFunction​​​​@RequestMapping​

​RouterFunctions.route()​​提供便于创建路由器的路由器构建器, 如以下示例所示:

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();


public class PersonHandler {

// ...

public Mono<ServerResponse> listPeople(ServerRequest request) {
// ...
}

public Mono<ServerResponse> createPerson(ServerRequest request) {
// ...
}

public Mono<ServerResponse> getPerson(ServerRequest request) {
// ...
}
}

使用创建路由器。​​route()​

一种运行 ais 将其转换为 an并安装它的方法 通过其中一个内置服务器适配器:​​RouterFunction​​​​HttpHandler​

  • ​RouterFunctions.toHttpHandler(RouterFunction)​
  • ​RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)​

大多数应用程序都可以通过 WebFlux Java 配置运行,请参阅运行服务器。

1.5.2. 处理程序函数

网络MVC

​ServerRequest​​并且是不可变的接口,提供 JDK 8 友好 访问 HTTP 请求和响应。 请求和响应都提供反应流背压 对着身体流。 请求正文由反应器表示。 响应正文用任何反应式流表示,包括和。 有关此内容的更多信息,请参阅反应式库。​​ServerResponse​​​​Flux​​​​Mono​​​​Publisher​​​​Flux​​​​Mono​

服务器请求

​ServerRequest​​提供对 HTTP 方法、URI、标头和查询参数的访问, 而通过方法提供对身体的访问。​​body​

以下示例将请求正文提取为 a:​​Mono<String>​

Mono<String> string = request.bodyToMono(String.class);

以下示例将主体提取为 a(或 ain Kotlin), 其中对象是从某种序列化形式(如 JSON 或 XML)解码的:​​Flux<Person>​​​​Flow<Person>​​​​Person​

Flux<Person> people = request.bodyToFlux(Person.class);

前面的示例是使用更通用的快捷方式, 它接受功能策略接口。实用程序类提供对许多实例的访问。例如,前面的示例可以 也写如下:​​ServerRequest.body(BodyExtractor)​​​​BodyExtractor​​​​BodyExtractors​

Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));

以下示例演示如何访问表单数据:

Mono<MultiValueMap<String, String>> map = request.formData();

以下示例显示如何以地图形式访问多部分数据:

Mono<MultiValueMap<String, Part>> map = request.multipartData();

以下示例演示如何以流式处理方式访问多个部分数据,一次访问一个:

Flux<PartEvent> allPartEvents = request.bodyToFlux(PartEvent.class);
allPartsEvents.windowUntil(PartEvent::isLast)
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> {
if (signal.hasValue()) {
PartEvent event = signal.get();
if (event instanceof FormPartEvent formEvent) {
String value = formEvent.value();
// handle form field
}
else if (event instanceof FilePartEvent fileEvent) {
String filename = fileEvent.filename();
Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
// handle file upload
}
else {
return Mono.error(new RuntimeException("Unexpected event: " + event));
}
}
else {
return partEvents; // either complete or error signal
}
}));

请注意,必须完全使用、中继或释放对象的正文内容,以避免内存泄漏。​​PartEvent​

服务器响应

​ServerResponse​​提供对 HTTP 响应的访问,并且由于它是不可变的,因此您可以使用 创建它的方法。您可以使用构建器设置响应状态,添加响应 标头,或提供正文。以下示例使用 JSON 创建 200(正常)响应 内容:​​build​

Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);

以下示例演示如何生成带有标头且没有正文的 201 (CREATED) 响应:​​Location​

爪哇岛

科特林

URI location = ...
ServerResponse.created(location).build();

根据所使用的编解码器,可以传递提示参数以自定义如何 正文已序列化或反序列化。例如,要指定杰克逊 JSON 视图,请执行以下操作:

ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);

处理程序类

我们可以将处理程序函数编写为 lambda,如以下示例所示:

HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");

​listPeople​​​是一个处理程序函数,它将在存储库中找到的所有对象返回为 杰森。​​Person​

​createPerson​​​是一个处理程序函数,用于在请求正文中存储 newcontains。 注意返回:一个空的发出 从请求中读取并存储人员时的完成信号。因此,我们使用该方法在收到完成信号时发送响应(即 当已保存时)。​​Person​​​​PersonRepository.savePerson(Person)​​​​Mono<Void>​​​​Mono​​​​build(Publisher<Void>)​​​​Person​

​getPerson​​​是一个处理程序函数,它返回一个人,由路径标识 变量。我们从存储库中检索它并创建一个 JSON 响应,如果是 发现。如果未找到,我们习惯返回 404 未找到响应。​​id​​​​Person​​​​switchIfEmpty(Mono<T>)​

这很方便,但是在应用程序中,我们需要多个函数和多个内联 lambda可能会变得混乱。 因此,将相关的处理程序函数组合到一个处理程序类中很有用,该处理程序类 在基于注释的应用程序中具有类似的角色。 例如,以下类公开了一个反应式存储库:​​@Controller​​​​Person​

public class PersonHandler {

private final PersonRepository repository;

public PersonHandler(PersonRepository repository) {
this.repository = repository;
}

public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}

public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}

public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
return repository.getPerson(personId)
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
.switchIfEmpty(ServerResponse.notFound().build());
}
}

​listPeople​​​是一个处理程序函数,它将在存储库中找到的所有对象返回为 杰森。​​Person​

​createPerson​​​是一个处理程序函数,用于在请求正文中存储 newcontains。 请注意,这是一个没有返回类型的挂起函数。​​Person​​​​PersonRepository.savePerson(Person)​

​getPerson​​​是一个处理程序函数,它返回一个人,由路径标识 变量。我们从存储库中检索它并创建一个 JSON 响应,如果是 发现。如果未找到,我们将返回 404 未找到响应。​​id​​​​Person​

验证

功能端点可以使用 Spring 的验证工具来 将验证应用于请求正文。例如,给定一个自定义的 Spring验证器实现:​​Person​

public class PersonHandler {

private final Validator validator = new PersonValidator();

// ...

public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate);
return ok().build(repository.savePerson(person));
}

private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString());
}
}
}

创建实例。​​Validator​

应用验证。

引发 400 响应的异常。

处理程序还可以通过创建和注入来使用标准 Bean 验证 API (JSR-303) 基于的全局实例。 请参阅弹簧验证。​​Validator​​​​LocalValidatorFactoryBean​

1.5.3. ​​RouterFunction​

网络MVC

路由器函数用于将请求路由到相应的请求。 通常,您不会自己编写路由器函数,而是使用实用程序类上的方法来创建一个。(无参数)为您提供用于创建路由器的流畅构建器 功能,而提供直接的方法 以创建路由器。​​HandlerFunction​​​​RouterFunctions​​​​RouterFunctions.route()​​​​RouterFunctions.route(RequestPredicate, HandlerFunction)​

通常,建议使用构建器,因为它提供 适用于典型映射场景的便捷快捷方式,无需难以发现 静态导入。 例如,路由器函数构建器提供了为 GET 请求创建映射的方法;和开机自检。​​route()​​​​GET(String, HandlerFunction)​​​​POST(String, HandlerFunction)​

除了基于 HTTP 方法的映射之外,路由构建器还提供了一种引入其他方法的方法 映射到请求时的谓词。 对于每个 HTTP 方法,都有一个重载变体,它采用 aas a 参数,但可以表示哪些附加约束。​​RequestPredicate​

谓词

你可以编写自己的,但是实用程序类 提供常用的实现,基于请求路径、HTTP 方法、内容类型、 等等。 以下示例使用请求谓词创建基于 标头的约束:​​RequestPredicate​​​​RequestPredicates​​​​Accept​

RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();

您可以使用以下命令将多个请求谓词组合在一起:

  • ​RequestPredicate.and(RequestPredicate)​​— 两者必须匹配。
  • ​RequestPredicate.or(RequestPredicate)​​— 两者都可以匹配。

许多谓词都是由 from 组成的。 例如,由 and 组成。 上面显示的示例还使用两个请求谓词,因为构建器在内部使用,并将其与谓词组合在一起。​​RequestPredicates​​​​RequestPredicates.GET(String)​​​​RequestPredicates.method(HttpMethod)​​​​RequestPredicates.path(String)​​​​RequestPredicates.GET​​​​accept​

路线

路由器功能按顺序评估:如果第一个路由不匹配,则 第二个是评估,依此类推。 因此,在一般路由之前声明更具体的路由是有意义的。 当将路由器功能注册为 Spring bean 时,这一点也很重要,就像 稍后再描述。 请注意,此行为不同于基于注释的编程模型,其中 自动选取“最具体”的控制器方法。

使用路由器函数生成器时,所有定义的路由都组合成一个返回的路由。 还有其他方法可以将多个路由器功能组合在一起:​​RouterFunction​​​​build()​

  • ​add(RouterFunction)​​在建造者上RouterFunctions.route()
  • ​RouterFunction.and(RouterFunction)​
  • ​RouterFunction.andRoute(RequestPredicate, HandlerFunction)​​— 嵌套的快捷方式。RouterFunction.and()RouterFunctions.route()

以下示例显示了四个路由的组合:

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> otherRoute = ...

RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.add(otherRoute)
.build();

​GET /person/{id}​​​与 JSON 匹配的标头被路由到​​Accept​​​​PersonHandler.getPerson​

​GET /person​​​与 JSON 匹配的标头被路由到​​Accept​​​​PersonHandler.listPeople​

​POST /person​​​没有映射到其他谓词,并且​​PersonHandler.createPerson​

​otherRoute​​是在其他地方创建并添加到构建的路由中的路由器功能。

嵌套路由

一组路由器函数通常具有共享谓词,例如 共享路径。在上面的示例中,共享谓词将是一个路径谓词 匹配项,由其中三条路由使用。使用批注时,应删除 通过使用映射到的类型级批注进行此重复。在 WebFlux.fn 中,路径谓词可以通过 路由器功能生成器。例如,上面示例的最后几行可以是 通过使用嵌套路由,通过以下方式进行了改进:​​/person​​​​@RequestMapping​​​​/person​​​​path​

RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST(handler::createPerson))
.build();

请注意,第二个参数是采用路由器构建器的使用者。​​path​

尽管基于路径的嵌套是最常见的,但您可以使用 构建器上的方法。 上面仍然包含共享标头谓词形式的一些重复。 我们可以通过将该方法与以下方法一起使用来进一步改进:​​nest​​​​Accept​​​​nest​​​​accept​

RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.build();

1.5.4. 运行服务器

网络MVC

如何在 HTTP 服务器中运行路由器功能?一个简单的选择是转换路由器 函数到使用以下方法之一:​​HttpHandler​

  • ​RouterFunctions.toHttpHandler(RouterFunction)​
  • ​RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)​

然后,您可以按照特定于服务器的说明,按照HttpHandler使用返回的多个服务器适配器。​​HttpHandler​

Spring Boot 也使用的一个更典型的选项是通过WebFlux Configuration 使用基于DispatcherHandler 的设置运行,该配置使用 Spring 配置来声明 处理请求所需的组件。WebFlux Java 配置声明以下内容 支持功能端点的基础架构组件:

  • ​RouterFunctionMapping​​:在春季检测一种或多种豆类 配置,对它们进行排序,通过它们进行组合,并将请求路由到生成的组合。RouterFunction<?>RouterFunction.andOtherRouterFunction
  • ​HandlerFunctionAdapter​​:letsinvoke a已映射到请求。DispatcherHandlerHandlerFunction
  • ​ServerResponseResultHandler​​:处理调用 aby 调用方法的结果。HandlerFunctionwriteToServerResponse

上述组件允许功能终结点适合请求 处理生命周期,并且(可能)与带注释的控制器并行运行,如果 任何声明。这也是通过Spring Boot WebFlux启用功能端点的方式。 起动机。​​DispatcherHandler​

以下示例显示了 WebFlux Java 配置(有关如何运行它,请参阅DispatcherHandler):

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}

@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}

// ...

@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// configure message conversion...
}

@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}

1.5.5. 过滤处理程序函数

网络MVC

可以通过在路由上使用、、或方法来筛选处理程序函数 函数生成器。 使用批注,您可以通过使用 a 或两者来实现类似的功能。 过滤器将应用于构建器构建的所有路径。 这意味着嵌套路由中定义的筛选器不适用于“顶级”路由。 例如,请考虑以下示例:​​before​​​​after​​​​filter​​​​@ControllerAdvice​​​​ServletFilter​

RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople)
.before(request -> ServerRequest.from(request)
.header("X-RequestHeader", "Value")
.build()))
.POST(handler::createPerson))
.after((request, response) -> logResponse(response))
.build();

添加自定义请求标头的筛选器仅适用于两个 GET 路由。​​before​

记录响应的筛选器将应用于所有路由,包括嵌套路由。​​after​

路由器构建器上的方法需要: 接受 aandand 返回 a 的函数。 处理程序函数参数表示链中的下一个元素。 这通常是路由到的处理程序,但也可以是另一个处理程序 如果应用了多个,则进行筛选。​​filter​​​​HandlerFilterFunction​​​​ServerRequest​​​​HandlerFunction​​​​ServerResponse​

现在我们可以向我们的路由添加一个简单的安全过滤器,假设我们有 可以确定是否允许特定路径。 以下示例演示如何执行此操作:​​SecurityManager​

SecurityManager securityManager = ...

RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();

前面的示例演示了调用是可选的。 我们只允许在允许访问时运行处理程序函数。​​next.handle(ServerRequest)​

除了在路由器函数构建器上使用该方法外,还可以应用 通过以下方式过滤到现有路由器功能。​​filter​​​​RouterFunction.filter(HandlerFilterFunction)​

1.6. URI 链接

网络MVC

本节描述了 Spring 框架中可用于准备 URI 的各种选项。

1.6.1. URI组件

Spring MVC 和 Spring WebFlux

​UriComponentsBuilder​​帮助使用变量从 URI 模板构建 URI,如以下示例所示:

UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.build();

URI uri = uriComponents.expand("Westin", "123").toUri();

具有 URI 模板的静态工厂方法。

添加或替换 URI 组件。

请求对 URI 模板和 URI 变量进行编码。

构建一个。​​UriComponents​

展开变量并获取。​​URI​

前面的示例可以合并为一个链,并缩短为: 如以下示例所示:​​buildAndExpand​

URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri();

您可以通过直接转到 URI(这意味着编码)来进一步缩短它, 如以下示例所示:

爪哇岛

科特林

URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");

您可以使用完整的 URI 模板进一步缩短它,如以下示例所示:

URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123");

1.6.2. 乌里生成器

Spring MVC 和 Spring WebFlux

UriComponentsBuilder实现。您可以依次使用 a 创建 a。一起,并提供一种可插拔机制,用于从 URI 模板构建 URI,基于 共享配置,例如基本 URL、编码首选项和其他详细信息。​​UriBuilder​​​​UriBuilder​​​​UriBuilderFactory​​​​UriBuilderFactory​​​​UriBuilder​

您可以配置并使用a来自定义 URIs.is 默认值的准备 实现使用内部和 公开共享配置选项。​​RestTemplate​​​​WebClient​​​​UriBuilderFactory​​​​DefaultUriBuilderFactory​​​​UriBuilderFactory​​​​UriComponentsBuilder​

以下示例演示如何配置:​​RestTemplate​

// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

以下示例配置:​​WebClient​

// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

WebClient client = WebClient.builder().uriBuilderFactory(factory).build();

此外,您也可以直接使用。它类似于 using,但不是静态工厂方法,它是一个实际实例 包含配置和首选项,如以下示例所示:​​DefaultUriBuilderFactory​​​​UriComponentsBuilder​

String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);

URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");

1.6.3. URI 编码

Spring MVC 和 Spring WebFlux

​UriComponentsBuilder​​在两个级别公开编码选项:

  • UriComponentsBuilder#encode(): 首先对 URI 模板进行预编码,然后在展开时严格编码 URI 变量。
  • UriComponents#encode(): 在展开 URI 变量后​URI 组件进行编码。

这两个选项都将非 ASCII 字符和非法字符替换为转义的八位字节。但是,第一个选项 还会替换 URI 变量中出现的具有保留含义的字符。

在大多数情况下,第一个选项可能会给出预期的结果,因为它处理 URI 变量作为要完全编码的不透明数据,而第二个选项在 URI 变量确实有意包含保留字符。第二个选项也很有用 当根本不扩展 URI 变量时,因为这也会对任何 顺便看起来像一个 URI 变量。

以下示例使用第一个选项:

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("New York", "foo+bar")
.toUri();

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"

您可以通过直接转到 URI(这意味着编码)来缩短前面的示例, 如以下示例所示:

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar");

您可以使用完整的 URI 模板进一步缩短它,如以下示例所示:

URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar");

在内部扩展和编码 URI 模板 战略。两者都可以使用自定义策略进行配置, 如以下示例所示:​​WebClient​​​​RestTemplate​​​​UriBuilderFactory​

String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();

该实现在内部使用 展开并编码 URI 模板。作为工厂,它提供了一个配置位置 基于以下编码模式之一的编码方法:​​DefaultUriBuilderFactory​​​​UriComponentsBuilder​

  • ​TEMPLATE_AND_VALUES​​:用途,对应于 前面列表中的第一个选项,用于预编码 URI 模板并在以下情况下严格编码 URI 变量 扩大。UriComponentsBuilder#encode()
  • ​VALUES_ONLY​​:不对 URI 模板进行编码,而是应用严格编码 到 URI 变量之前将它们扩展到 模板。UriUtils#encodeUriVariables
  • ​URI_COMPONENT​​:用于与前面列表中的第二个选项对应的 在展开 URI 变量后对URI 组件值进行编码。UriComponents#encode()
  • ​NONE​​:不应用任何编码。

泰斯将用于历史 原因和向后兼容性。默认值上有 在,从中改为 5.0.x toin 5.1.​​RestTemplate​​​​EncodingMode.URI_COMPONENT​​​​WebClient​​​​DefaultUriBuilderFactory​​​​EncodingMode.URI_COMPONENT​​​​EncodingMode.TEMPLATE_AND_VALUES​

1.7. CORS

网络MVC

Spring WebFlux 允许您处理 CORS(跨源资源共享)。本节 介绍了如何执行此操作。

1.7.1. 简介

网络MVC

出于安全原因,浏览器禁止对当前源之外的资源进行 AJAX 调用。 例如,您可以将银行帐户放在一个标签页中,evil.com 放在另一个标签页中。脚本 从 evil.com 应该无法使用您的银行 API 向您的银行 API 发出 AJAX 请求 凭据 — 例如,从您的帐户中提取资金!

跨源资源共享 (CORS) 是大多数浏览器实现的W3C 规范,允许您指定 授权哪种跨域请求,而不是使用安全性较低且安全性较低的请求 基于 IFRAME 或 JSONP 的强大解决方法。

1.7.2. 处理

网络MVC

CORS 规范区分了预检请求、简单请求和实际请求。 要了解 CORS 的工作原理,您可以阅读本文,其中 许多其他人,或查看规范以获取更多详细信息。

Spring WebFluximplementations为CORS提供了内置的支持。成功后 将请求映射到处理程序,检查 CORS 配置的 给定请求和处理程序,并采取进一步操作。处理印前检查请求 直接,而简单和实际的 CORS 请求被拦截、验证,并具有 所需的 CORS 响应标头集。​​HandlerMapping​​​​HandlerMapping​

为了启用跨源请求(即标头存在并且 与请求的主机不同),您需要有一些显式声明的 CORS 配置。如果未找到匹配的 CORS 配置,则预检请求为 拒绝。不会将 CORS 标头添加到简单和实际 CORS 请求的响应中 因此,浏览器会拒绝它们。​​Origin​

每个都可以使用基于 URL 模式的映射单独配置。在大多数情况下,应用程序 使用 WebFlux Java 配置来声明此类映射,从而生成单个 全局地图传递给所有实现。​​HandlerMapping​​​​CorsConfiguration​​​​HandlerMapping​

您可以将该级别的全局 CORS 配置与更多内容相结合 细粒度的处理程序级 CORS 配置。例如,带注释的控制器可以使用 类级或方法级注释(其他处理程序可以实现)。​​HandlerMapping​​​​@CrossOrigin​​​​CorsConfigurationSource​

组合全局和本地配置的规则通常是累加的,例如, 所有全球和本地起源。对于只能有一个值的属性 接受,例如 AND,局部将覆盖全局值。请参阅 CorsConfiguration#combine(CorsConfiguration)了解更多详情。​​allowCredentials​​​​maxAge​

1.7.3. ​​@CrossOrigin​

网络MVC

@CrossOrigin注释在带注释的控制器方法上启用跨源请求,因为 以下示例显示:

@RestController
@RequestMapping("/account")
public class AccountController {

@CrossOrigin
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}

@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}

在类级别使用。​​@CrossOrigin​

在方法级别使用。​​@CrossOrigin​

默认情况下,允许:​​@CrossOrigin​

  • 所有起源。
  • 所有标头。
  • 控制器方法映射到的所有 HTTP 方法。

​allowCredentials​​默认情况下不启用,因为这会建立信任级别 暴露敏感的用户特定信息(例如 cookie 和 CSRF 令牌)和 应仅在适当时使用。启用时必须 设置为一个或多个特定域(但不是特殊值)或 该属性可用于匹配一组动态的源。​​allowOrigins​​​​"*"​​​​allowOriginPatterns​

​maxAge​​设置为 30 分钟。

​@CrossOrigin​​在类级别也受支持,并且由所有方法继承。 以下示例指定某个域并设置为小时:​​maxAge​

@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}

@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}

在类级别使用。​​@CrossOrigin​

在方法级别使用。​​@CrossOrigin​

您可以在类和方法级别使用, 如以下示例所示:​​@CrossOrigin​

@CrossOrigin(maxAge = 3600) 
@RestController
@RequestMapping("/account")
public class AccountController {

@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}

@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}

1.7.4. 全局配置

网络MVC

除了细粒度的控制器方法级配置之外,您可能还希望 还要定义一些全局 CORS 配置。您可以在任何上单独设置基于 URL 的映射。但是,大多数应用程序使用 WebFlux Java配置来做到这一点。​​CorsConfiguration​​​​HandlerMapping​

默认情况下,全局配置启用以下内容:

  • 所有起源。
  • 所有标头。
  • ​GET​​,和方法。HEADPOST

​allowedCredentials​​默认情况下不启用,因为这会建立信任级别 暴露敏感的用户特定信息(例如 cookie 和 CSRF 令牌)和 应仅在适当时使用。启用时必须 设置为一个或多个特定域(但不是特殊值)或 该属性可用于匹配一组动态的源。​​allowOrigins​​​​"*"​​​​allowOriginPatterns​

​maxAge​​设置为 30 分钟。

要在 WebFlux Java 配置中启用 CORS,可以使用回调, 如以下示例所示:​​CorsRegistry​

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {

registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);

// Add more mappings...
}
}

1.7.5. CORS ​​WebFilter​

网络MVC

您可以通过内置的CorsWebFilter应用CORS支持,这是一个 非常适合功能端点。

若要配置筛选器,可以声明 abean 并将 a传递给其构造函数,如以下示例所示:​​CorsWebFilter​​​​CorsConfigurationSource​

@Bean
CorsWebFilter corsFilter() {

CorsConfiguration config = new CorsConfiguration();

// Possibly...
// config.applyPermitDefaultValues()

config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);

return new CorsWebFilter(source);
}

1.8. 错误响应

网络MVC

REST 服务的常见要求是在错误正文中包含详细信息 反应。Spring 框架支持“HTTP API 的问题详细信息” 规范,RFC 7807。

以下是此支持的主要抽象:

  • ​ProblemDetail​​— RFC 7807 问题详细信息的表示;一个简单的容器 对于规范中定义的标准字段和非标准字段。
  • ​ErrorResponse​​— 公开 HTTP 错误响应详细信息(包括 HTTP)的合约 状态、响应标头和 RFC 7807 格式的正文;这允许例外 封装并公开它们如何映射到 HTTP 响应的详细信息。所有春季网络通量 异常实现这一点。
  • ​ErrorResponseException​​— 基本实现 其他人 可以用作方便的基类。ErrorResponse
  • ​ResponseEntityExceptionHandler​​— 方便的基类,用于处理所有Spring WebFlux异常@ControllerAdvice, 和 any,并使用正文呈现错误响应。ErrorResponseException

1.8.1. 渲染

网络MVC

你可以从任何或从任何返回 呈现 RFC 7807 响应的任意方法。这将按如下方式处理:​​ProblemDetail​​​​ErrorResponse​​​​@ExceptionHandler​​​​@RequestMapping​

  • 的属性确定 HTTP 状态。statusProblemDetail
  • 属性是从当前 URL 路径设置的,如果不是 已经设置好了。instanceProblemDetail
  • 对于内容谈判,杰克逊更喜欢 渲染 a 时“application/problem+json”而不是“application/json”, 如果未找到兼容的媒体类型,也会回退到它。HttpMessageConverterProblemDetail

要为 Spring WebFlux 异常和任何异常启用 RFC 7807 响应,请在 Spring 配置中扩展并声明为 an@ControllerAdvice。处理程序 有一个处理任何异常的方法,它 包括所有内置 Web 异常。您可以添加更多异常处理方法,并且 使用受保护的方法将任何异常映射到 A。​​ErrorResponseException​​​​ResponseEntityExceptionHandler​​​​@ExceptionHandler​​​​ErrorResponse​​​​ProblemDetail​

1.8.2. 非标准字段

网络MVC

您可以通过以下两种方式之一使用非标准字段扩展 RFC 7807 响应。

一、插入“属性”。使用杰克逊时 库,Spring 框架寄存器确保这一点 “属性”被解包并呈现为顶级 JSON 属性在 响应,同样,反序列化期间的任何未知属性都插入到 这。​​Map​​​​ProblemDetail​​​​ProblemDetailJacksonMixin​​​​Map​​​​Map​

您还可以扩展以添加专用的非标准属性。 复制构造函数不允许子类使其易于创建 从现有的。这可以集中完成,例如,从将异常重新创建到具有附加非标准字段的子类中。​​ProblemDetail​​​​ProblemDetail​​​​ProblemDetail​​​​@ControllerAdvice​​​​ResponseEntityExceptionHandler​​​​ProblemDetail​

1.8.3. 国际化

网络MVC

国际化错误响应详细信息和良好做法是一个常见要求 以自定义 Spring WebFlux 异常的问题详细信息。支持如下:

  • 每个公开消息代码和参数以解析“详细信息”字段 通过消息源。 实际的消息代码值使用占位符进行参数化,e.g.to 从参数扩展。ErrorResponse"HTTP method {0} not supported"
  • 每个还公开一个消息代码来解析“标题”字段。ErrorResponse
  • ​ResponseEntityExceptionHandler​​使用消息代码和参数来解析 “详细信息”和“标题”字段。

默认情况下,“detail”字段的消息代码是“problemDetail”。 + 完全 限定的异常类名。某些异常可能会在 在这种情况下,后缀将添加到默认消息代码中。下表列出了消息 Spring WebFlux 异常的参数和代码:

例外

消息代码

消息代码参数

​UnsupportedMediaTypeStatusException​

(默认)

​{0}​​​不支持的媒体类型,支持的媒体类型列表​​{1}​

​UnsupportedMediaTypeStatusException​

(默认值)+ “.parseError”

​MissingRequestValueException​

(默认)

​{0}​​​值的标签(例如“请求标头”、“cookie 值”等)、值名称​​{1}​

​UnsatisfiedRequestParameterException​

(默认)

​{0}​​参数条件列表

​WebExchangeBindException​

(默认)

​{0}​​​全局错误列表,字段错误列表。 其中每个错误的消息代码和参数也已解决 通过。​​{1}​​​​BindingResult​​​​MessageSource​

​NotAcceptableStatusException​

(默认)

​{0}​​支持的媒体类型列表

​NotAcceptableStatusException​

(默认值)+ “.parseError”

​ServerErrorException​

(默认)

​{0}​​提供给类构造函数的失败原因

​MethodNotAllowedException​

(默认)

​{0}​​​当前HTTP方法,支持的HTTP方法列表​​{1}​

默认情况下,“title”字段的消息代码是“problemDetail.title”。 + 完全 限定的异常类名。

1.8.4. 客户端处理

网络MVC

客户端应用程序在使用时可以捕获 或者当使用 和 时,使用他们的方法将错误响应正文解码为任何目标类型,例如 或 的子类。​​WebClientResponseException​​​​WebClient​​​​RestClientResponseException​​​​RestTemplate​​​​getResponseBodyAs​​​​ProblemDetail​​​​ProblemDetail​

1.9. 网络安全

网络MVC

Spring 安全项目提供支持 用于保护 Web 应用程序免受恶意。查看春季安全性 参考文档,包括:

  • 网络通量安全
  • 网络通量测试支持
  • 企业社会责任保护
  • 安全响应标头

1.10. HTTP 缓存

网络MVC

HTTP 缓存可以显著提高 Web 应用程序的性能。HTTP 缓存 围绕响应标头和后续条件请求 标头,例如 and.advises private(例如,浏览器) 公共(例如代理)缓存如何缓存和重用响应。使用标题 提出可能导致没有正文的 304 (NOT_MODIFIED) 的有条件请求, 如果内容没有更改。可以看作是更复杂的继承者 标题。​​Cache-Control​​​​Last-Modified​​​​ETag​​​​Cache-Control​​​​ETag​​​​ETag​​​​Last-Modified​

本节介绍Spring WebFlux中可用的HTTP缓存相关选项。

1.10.1. ​​CacheControl​

网络MVC

缓存控件提供对 配置与标头相关的设置,并被接受为参数 在许多地方:Cache-Control

  • 控制器
  • 静态资源

虽然RFC 7234描述了所有可能的内容 响应标头的指令,该类型采用 面向用例的方法,侧重于常见方案,如以下示例所示:​​Cache-Control​​​​CacheControl​

// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();

1.10.2. 控制器

网络MVC

控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为需要先计算资源的理论值,然后才能进行比较 针对条件请求标头。控制器可以将 anandsettings 添加到 ,如以下示例所示:​​lastModified​​​​ETag​​​​ETag​​​​Cache-Control​​​​ResponseEntity​

@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

Book book = findBook(id);
String version = book.getVersion();

return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
.eTag(version) // lastModified is also available
.body(book);
}

特定于应用程序的计算。

响应已设置为 304 (NOT_MODIFIED)。无需进一步处理。

继续处理请求。

前面的示例发送一个 304 (NOT_MODIFIED) 响应,如果比较 到条件请求标头表示内容未更改。否则,会将标头添加到响应中。​​ETag​​​​Cache-Control​

您还可以检查控制器中的条件请求标头, 如以下示例所示:

@RequestMapping
public String myHandleMethod(ServerWebExchange exchange, Model model) {

long eTag = ...

if (exchange.checkNotModified(eTag)) {
return null;
}

model.addAttribute(...);
return "myViewName";
}

特定于应用程序的计算。

响应已设置为 304 (NOT_MODIFIED)。无需进一步处理。

继续处理请求。

有三种变体可用于检查条件请求与值、值或两者。对于条件请求,您可以将响应设置为 304(NOT_MODIFIED)。对于条件、和,您可以改为设置响应 到 412 (PRECONDITION_FAILED) 以防止并发修改。​​eTag​​​​lastModified​​​​GET​​​​HEAD​​​​POST​​​​PUT​​​​DELETE​

1.10.3. 静态资源

网络MVC

您应该使用 aand 条件响应标头提供静态资源 以获得最佳性能。请参阅有关配置静态资源的部分。​​Cache-Control​

1.11. 查看技术

网络MVC

Spring WebFlux 中视图技术的使用是可插拔的。无论您是否决定 使用Thymeleaf,FreeMarker或其他一些视图技术主要是 配置更改。本章介绍与 Spring 集成的视图技术 网络通量。我们假设您已经熟悉视图分辨率。

1.11.1. 百里香叶

网络MVC

Thymeleaf是一个现代的服务器端Java模板引擎,强调自然HTML 可以通过双击在浏览器中预览的模板,这是非常 有助于在 UI 模板上独立工作(例如,由设计人员进行),而无需 正在运行的服务器。百里香叶提供了广泛的功能,并且正在积极开发 并维护。有关更完整的介绍,请参阅Thymeleaf项目主页。

Thymeleaf 与 Spring WebFlux 的集成由 Thymeleaf 项目管理。这 配置涉及一些 Bean 声明,例如、和。有关更多详细信息,请参阅Thymeleaf+Spring和 WebFlux 集成公告。​​SpringResourceTemplateResolver​​​​SpringWebFluxTemplateEngine​​​​ThymeleafReactiveViewResolver​

1.11.2. 自由标记

网络MVC

Apache FreeMarker是一个模板引擎,用于生成任何 从 HTML 到电子邮件和其他文本输出的一种。Spring 框架内置了 集成使用 Spring WebFlux 和 FreeMarker 模板。

查看配置

网络MVC

下面的示例演示如何将 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/freemarker");
return configurer;
}
}

您的模板需要存储在指定的目录中, 如前面的示例所示。给定上述配置,如果您的控制器 返回视图名称,解析程序查找模板。​​FreeMarkerConfigurer​​​​welcome​​​​classpath:/templates/freemarker/welcome.ftl​

自由标记配置

网络MVC

您可以通过设置适当的 bean 将 FreeMarker 的“设置”和“共享变量”直接传递给 FreeMarkerobject(由 Spring 管理)。 沂豆上的属性。该属性要求 aobject,并且该属性需要 a。以下示例演示如何使用:​​Configuration​​​​FreeMarkerConfigurer​​​​freemarkerSettings​​​​java.util.Properties​​​​freemarkerVariables​​​​java.util.Map​​​​FreeMarkerConfigurer​

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

// ...

@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
Map<String, Object> variables = new HashMap<>();
variables.put("xml_escape", new XmlEscape());

FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates");
configurer.setFreemarkerVariables(variables);
return configurer;
}
}

请参阅 FreeMarker 文档,了解适用于 的设置和变量的详细信息 对象。​​Configuration​

表单处理

网络MVC

Spring 提供了一个用于 JSP 的标签库,其中包含 aelement。此元素主要允许窗体显示来自 表单支持对象,并显示来自 ain 的失败验证的结果 网络或业务层。Spring 在 FreeMarker 中也支持相同的功能, 具有用于自己生成表单输入元素的更多便利宏。​​<spring:bind/>​​​​Validator​

绑定宏

网络MVC

在文件中维护一组标准的宏 FreeMarker,因此它们始终可用于适当配置的应用程序。​​spring-webflux.jar​

Spring 模板库中定义的一些宏被认为是内部的 (私有),但宏定义中不存在这样的范围,使所有宏都可见 调用代码和用户模板。以下各节仅重点介绍宏 您需要直接从模板中调用。如果要查看宏代码 直接,该文件被调用并且位于包中。​​spring.ftl​​​​org.springframework.web.reactive.result.view.freemarker​

有关绑定支持的其他详细信息,请参阅简单 SpringMVC 的绑定。

表单宏

有关 Spring 对 FreeMarker 模板的表单宏支持的详细信息,请参阅以下内容 Spring MVC 文档的部分。

  • 输入宏
  • 输入字段
  • 选择字段
  • 网页转义

1.11.3. 脚本视图

网络MVC

Spring 框架有一个内置的集成,可以将 Spring WebFlux 与任何 可以在JSR-223Java 脚本引擎上运行的模板库。 下表显示了我们在不同的脚本引擎上测试的模板库:

脚本库

脚本引擎

车把

纳肖恩

胡子

纳肖恩

反应

纳肖恩

EJS

纳肖恩

再培训局

JRuby

字符串模板

杰通

Kotlin 脚本模板化

科特林

要求

网络MVC

您需要在类路径上安装脚本引擎,其详细信息因脚本引擎而异:

  • NashornJavaScript 引擎提供了 爪哇 8+。强烈建议使用可用的最新更新版本。
  • JRuby 应该作为 Ruby支持的依赖项添加。
  • Jython应该作为Python支持的依赖项添加。
  • ​org.jetbrains.kotlin:kotlin-script-util​​应添加依赖项和包含 aline 的文件以支持 Kotlin 脚本。有关更多详细信息,请参阅此示例。META-INF/services/javax.script.ScriptEngineFactoryorg.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory

您需要具有脚本模板库。对于 JavaScript 来说,一种方法是 通过网络罐。

脚本模板

网络MVC

您可以声明 abean 来指定要使用的脚本引擎, 要加载的脚本文件、要调用哪个函数来呈现模板等。 以下示例使用 Mustache 模板和 Nashorn JavaScript 引擎:​​ScriptTemplateConfigurer​

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}

@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("mustache.js");
configurer.setRenderObject("Mustache");
configurer.setRenderFunction("render");
return configurer;
}
}

使用以下参数调用该函数:​​render​

  • ​String template​​:模板内容
  • ​Map model​​:视图模型
  • ​RenderingContext renderingContext​​:提供对应用程序上下文、区域设置、模板加载器和 网址(自 5.0 起)

​Mustache.render()​​与此签名本机兼容,因此可以直接调用它。

如果您的模板技术需要一些自定义,则可以提供一个脚本 实现自定义呈现函数。例如,Handlerbars在使用模板之前需要编译模板,并且需要一个polyfill来模拟一些 浏览器功能在服务器端脚本引擎中不可用。 以下示例演示如何设置自定义呈现函数:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}

@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}

​polyfill.js​​​仅定义车把正常运行所需的对象, 如以下代码片段所示:​​window​

var window = {};

此基本实现在使用模板之前对其进行编译。一部作品 现成的实现还应存储和重用缓存的模板或预编译的模板。 这可以在脚本端完成,也可以完成您需要的任何自定义(管理 例如,模板引擎配置)。 以下示例演示如何编译模板:​​render.js​

function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}

查看 Spring 框架单元测试、Java 和资源, 了解更多配置示例。

1.11.4. JSON 和 XML

网络MVC

出于内容协商的目的,能够交替使用很有用 在使用 HTML 模板或其他格式(如 JSON 或 XML)呈现模型之间, 取决于客户端请求的内容类型。为了支持这样做,Spring WebFlux 提供,您可以使用它插入任何可用的编解码器,例如, 或。​​HttpMessageWriterView​​​​spring-web​​​​Jackson2JsonEncoder​​​​Jackson2SmileEncoder​​​​Jaxb2XmlEncoder​

与其他视图技术不同,它不需要将视图配置为默认视图。您可以 配置一个或多个此类默认视图,包装不同的实例 或者。与请求的内容类型匹配的内容类型在运行时使用。​​HttpMessageWriterView​​​​ViewResolver​​​​HttpMessageWriter​​​​Encoder​

在大多数情况下,一个模型包含多个属性。要确定要序列化哪一个, 您可以使用要用于的模型属性的名称进行配置 渲染。如果模型仅包含一个属性,则使用该属性。​​HttpMessageWriterView​

1.12. 网络通量配置

网络MVC

WebFlux Java 配置声明了处理所需的组件 带有注释的控制器或功能终结点的请求,并提供 API 来 自定义配置。这意味着您不需要了解底层 由 Java 配置创建的 bean。但是,如果您想了解它们, 您可以在其中看到它们或阅读有关它们的更多信息 在特殊的豆类类型中。​​WebFluxConfigurationSupport​

对于配置API 中不可用的更高级的自定义,您可以 通过高级配置模式获得对配置的完全控制。

1.12.1. 启用网络通量配置

网络MVC

您可以在 Java 配置中使用注释,如以下示例所示:​​@EnableWebFlux​

@Configuration
@EnableWebFlux
public class WebConfig {
}

前面的示例注册了许多 Spring WebFlux基础结构 bean并适应依赖关系。 在类路径上可用 — 用于 JSON、XML 和其他。

1.12.2. 网络通量配置 API

网络MVC

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

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

// Implement configuration methods...
}

1.12.3. 转换、格式化

网络MVC

默认情况下,会安装各种数字和日期类型的格式化程序以及支持 用于自定义 viaandon 字段。​​@NumberFormat​​​​@DateTimeFormat​

要在 Java 配置中注册自定义格式化程序和转换器,请使用以下命令:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
// ...
}

}

默认情况下,Spring WebFlux 在解析和格式化日期时会考虑请求语言环境 值。这适用于日期表示为带有“输入”形式的字符串的表单 领域。但是,对于“日期”和“时间”表单字段,浏览器使用定义的固定格式 在 HTML 规范中。对于这种情况,可以按如下方式自定义日期和时间格式:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
registrar.registerFormatters(registry);
}
}

1.12.4. 验证

网络MVC

默认情况下,如果存在Bean 验证 在类路径(例如,Hibernate验证器)上,注册为全局验证器以用于withandonmethod参数。​​LocalValidatorFactoryBean​​​​@Valid​​​​@Validated​​​​@Controller​

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

}

1.12.5. 内容类型解析器

网络MVC

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

下面的示例演示如何自定义请求的内容类型分辨率:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
// ...
}
}

1.12.6. HTTP 消息编解码器

网络MVC

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

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().maxInMemorySize(512 * 1024);
}
}

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

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

  • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES已禁用。
  • MapperFeature.DEFAULT_VIEW_INCLUSION被禁用。

如果在类路径中检测到以下已知模块,它还会自动注册这些模块:

  • jackson-datatype-joda:支持 Joda-Time 类型。
  • jackson-datatype-jsr310:支持 Java 8 日期和时间 API 类型。
  • jackson-datatype-jdk8:支持其他Java 8类型,例如。Optional
  • jackson-module-kotlin:支持 Kotlin 类和数据类。

1.12.7. 视图解析器

网络MVC

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

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// ...
}
}

Thehas具有Spring框架使用的视图技术的快捷方式 集成。以下示例使用 FreeMarker(它还需要配置 底层自由标记视图技术):​​ViewResolverRegistry​

@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);
}
}

支持内容协商和呈现其他格式 通过视图分辨率(除了 HTML),您可以配置一个或多个基于的默认视图 在实现上,它接受任何可用的编解码器。以下示例演示如何执行此操作:​​HttpMessageWriterView​​​​spring-web​

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

网络MVC

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

在下一个示例中,给定一个以 开头的请求,相对路径为 用于查找和提供相对于类路径的静态资源。资源 提供一年的未来到期,以确保最大限度地利用浏览器缓存 并减少了浏览器发出的 HTTP 请求。标题也是 已计算,如果存在,则返回状态代码。以下列表显示 示例:​​/resources​​​​/static​​​​Last-Modified​​​​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));
}

}

资源处理程序还支持资源解析器实现和资源转换器实现链, 可用于创建用于处理优化资源的工具链。

您可以使用基于 MD5 哈希的版本控制资源 URL 根据内容、固定应用程序版本或其他信息计算得出。A(MD5 哈希)是一个不错的选择,但有一些值得注意的例外(例如 与模块加载器一起使用的 JavaScript 资源)。​​VersionResourceResolver​​​​ContentVersionStrategy​

以下示例显示了如何在 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("/**"));
}

}

您可以使用重写 URL 并应用完整的解析器链和 转换器(例如,插入版本)。WebFlux 配置提供了可以注入到其他人中的 aso。​​ResourceUrlProvider​​​​ResourceUrlProvider​

与Spring MVC不同,目前在WebFlux中,没有办法透明地重写静态。 资源 URL,因为没有可以使用非阻塞链的视图技术 的旋转变压器和变压器。仅提供本地资源时,解决方法是直接使用(例如,通过自定义元素)和块。​​ResourceUrlProvider​

请注意,当同时使用两者(例如,Gzip,Brotli编码)和时,它们必须按该顺序注册,以确保基于内容 版本始终基于未编码的文件可靠地计算。​​EncodedResourceResolver​​​​VersionedResourceResolver​

对于WebJars,版本化 URL 是推荐和最有效的使用方式。 相关的资源位置是使用 Spring 引导配置的开箱即用(或者可以配置 手动通过),并且不需要添加依赖项。​​/webjars/jquery/1.2.0/jquery.min.js​​​​ResourceHandlerRegistry​​​​org.webjars:webjars-locator-core​

无版本的 URL 支持通过当库存在于类路径上时自动注册,代价是 可能会减慢应用程序启动速度的类路径扫描。解析器可以将 URL 重写为 包括 JAR 的版本,还可以与没有版本的传入 URL 匹配,例如 fromto。​​/webjars/jquery/jquery.min.js​​​​WebJarsResourceResolver​​​​org.webjars:webjars-locator-core​​​​/webjars/jquery/jquery.min.js​​​​/webjars/jquery/1.2.0/jquery.min.js​

1.12.9. 路径匹配

网络MVC

您可以自定义与路径匹配相关的选项。有关各个选项的详细信息,请参阅PathMatchConfigurerjavadoc。 以下示例演示如何使用:​​PathMatchConfigurer​

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseCaseSensitiveMatch(true)
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
}

1.12.10. 网络套接字服务

WebFlux Java 配置声明了 abean,它提供了 支持调用 WebSocket 处理程序。这意味着所有剩下的事情 处理 WebSocket 握手请求的顺序是将 aPass 映射到 URL 通过。​​WebSocketHandlerAdapter​​​​WebSocketHandler​​​​SimpleUrlHandlerMapping​

在某些情况下,可能需要创建带有 提供了允许配置 WebSocket 服务器属性的服务。 例如:​​WebSocketHandlerAdapter​​​​WebSocketService​

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public WebSocketService getWebSocketService() {
TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
strategy.setMaxSessionIdleTimeout(0L);
return new HandshakeWebSocketService(strategy);
}
}

1.12.11. 高级配置模式

网络MVC

​@EnableWebFlux​​进口:​​DelegatingWebFluxConfiguration​

  • 为 WebFlux 应用程序提供默认的 Spring 配置
  • 检测并委派给实现以自定义该配置。WebFluxConfigurer

对于高级模式,您可以直接从中删除和扩展而不是实现, 如以下示例所示:​​@EnableWebFlux​​​​DelegatingWebFluxConfiguration​​​​WebFluxConfigurer​

@Configuration
public class WebConfig extends DelegatingWebFluxConfiguration {

// ...
}

您可以保留现有方法,但现在也可以覆盖 Bean 声明 从基类中,并且仍然有任意数量的其他实现 类路径。​​WebConfig​​​​WebMvcConfigurer​

1.13. HTTP/2

网络MVC

HTTP/2 支持 Reactor Netty、Tomcat、Jetty 和 Undertow。但是,有 与服务器配置相关的注意事项。有关更多详细信息,请参阅HTTP/2 wiki 页面。

2. 网络客户端

Spring WebFlux 包含一个用于执行 HTTP 请求的客户端。 基于反应器的功能、流畅的 API,请参阅反应式库, 它支持异步逻辑的声明性组合,而无需处理 线程或并发。它是完全非阻塞的,它支持流媒体,并依赖于 也用于编码和编码的相同编解码器 在服务器端解码请求和响应内容。​​WebClient​

​WebClient​​需要一个 HTTP 客户端库来执行请求。有内置的 支持以下内容:

  • 反应器网
  • JDK HttpClient
  • Jetty Reactive HttpClient
  • Apache HttpComponents
  • 其他的可以通过插入。ClientHttpConnector

2.1. 配置

通过静态工厂方法之一创建 ais 的最简单方法:​​WebClient​

  • ​WebClient.create()​
  • ​WebClient.create(String baseUrl)​

您还可以使用其他选项:​​WebClient.builder()​

  • ​uriBuilderFactory​​:自定义为用作基本 URL。UriBuilderFactory
  • ​defaultUriVariables​​:展开 URI 模板时使用的默认值。
  • ​defaultHeader​​:每个请求的标头。
  • ​defaultCookie​​:针对每个请求的 Cookie。
  • ​defaultRequest​​:自定义每个请求。Consumer
  • ​filter​​:每个请求的客户端筛选器。
  • ​exchangeStrategies​​:HTTP 消息读取器/编写器自定义。
  • ​clientConnector​​:HTTP 客户端库设置。

例如:

WebClient client = WebClient.builder()
.codecs(configurer -> ... )
.build();

一旦构建,a是不可变的。但是,您可以克隆它并构建一个 修改后的副本如下:​​WebClient​

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.1. 最大内存大小

编解码器对缓冲数据有限制 内存以避免应用程序内存问题。默认情况下,这些设置为 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();

2.1.2. 反应堆网

要自定义反应器网络设置,请提供预配置的:​​HttpClient​

HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);

WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();

资源

默认情况下,参与持有的全局 Reactor Netty 资源,包括事件循环线程和连接池。 这是推荐的模式,因为事件循环首选固定的共享资源 并发。在此模式下,全局资源将保持活动状态,直到进程退出。​​HttpClient​​​​reactor.netty.http.HttpResources​

如果服务器与进程同步,则通常不需要显式 关闭。但是,如果服务器可以在进程内启动或停止(例如,Spring MVC 应用程序部署为 WAR),您可以声明一个 Spring 管理的 bean 类型(默认值)以确保反应器 当Springis关闭时,Netty全球资源被关闭, 如以下示例所示:​​ReactorResourceFactory​​​​globalResources=true​​​​ApplicationContext​

@Bean
public ReactorResourceFactory reactorResourceFactory() {
return new ReactorResourceFactory();
}

创建独立于全局的资源。

将构造函数与资源工厂一起使用。​​ReactorClientHttpConnector​

将连接器插入。​​WebClient.Builder​

您也可以选择不参与全球反应堆网络资源。然而 在这种模式下,责任在于您确保所有 Reactor Netty 客户端和服务器 实例使用共享资源,如以下示例所示:

@Bean
public ReactorResourceFactory resourceFactory() {
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setUseGlobalResources(false);
return factory;
}

@Bean
public WebClient webClient() {

Function<HttpClient, HttpClient> mapper = client -> {
// Further customizations...
};

ClientHttpConnector connector =
new ReactorClientHttpConnector(resourceFactory(), mapper);

return WebClient.builder().clientConnector(connector).build();
}

创建独立于全局的资源。

将构造函数与资源工厂一起使用。​​ReactorClientHttpConnector​

将连接器插入。​​WebClient.Builder​

超时

要配置连接超时:

HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);

WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();

要配置读取或写入超时:

HttpClient httpClient = HttpClient.create()
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10)));

// Create WebClient...

要为所有请求配置响应超时,请执行以下操作:

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.3. JDK http客户端

以下示例演示如何自定义 JDK:​​HttpClient​

HttpClient httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();

ClientHttpConnector connector =
new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory());

WebClient webClient = WebClient.builder().clientConnector(connector).build();

2.1.4. 码头

以下示例演示如何自定义码头设置:​​HttpClient​

HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);

WebClient webClient = WebClient.builder()
.clientConnector(new JettyClientHttpConnector(httpClient))
.build();

将构造函数与资源工厂一起使用。​​JettyClientHttpConnector​

将连接器插入。​​WebClient.Builder​

默认情况下,创建自己的资源(,,), 在进程退出调用的 oris 之前保持活动状态。​​HttpClient​​​​Executor​​​​ByteBufferPool​​​​Scheduler​​​​stop()​

您可以在 Jetty 客户端(和服务器)的多个实例之间共享资源,并且 确保在 Springis 关闭时关闭资源 声明 Spring 管理的 Bean 类型,如以下示例所示 显示:​​ApplicationContext​​​​JettyResourceFactory​

@Bean
public JettyResourceFactory resourceFactory() {
return new JettyResourceFactory();
}

@Bean
public WebClient webClient() {

HttpClient httpClient = new HttpClient();
// Further customizations...

ClientHttpConnector connector =
new JettyClientHttpConnector(httpClient, resourceFactory());

return WebClient.builder().clientConnector(connector).build();
}

将构造函数与资源工厂一起使用。​​JettyClientHttpConnector​

将连接器插入。​​WebClient.Builder​

2.1.5. http组件

以下示例演示如何自定义 Apache HttpComponents设置:​​HttpClient​

HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(...);
CloseableHttpAsyncClient client = clientBuilder.build();

ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);

WebClient webClient = WebClient.builder().clientConnector(connector).build();

2.2. ​​retrieve()​

该方法可用于声明如何提取响应。例如:​​retrieve()​

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

或者只得到身体:

WebClient client = WebClient.create("https://example.org");

Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);

获取解码对象流:

Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);

默认情况下,4xx 或 5xx 响应会产生包括 特定 HTTP 状态代码的子类。自定义错误的处理 响应,使用处理程序如下:​​WebClientResponseException​​​​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);

2.3. 交换

Theandmethods (orandin Kotlin) 对于需要更多控制的更高级的情况很有用,例如以不同的方式解码响应 根据响应状态:​​exchangeToMono()​​​​exchangeToFlux()​​​​awaitExchange { }​​​​exchangeToFlow { }​

Mono<Person> entityMono = client.get()
.uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Person.class);
}
else {
// Turn to error
return response.createError();
}
});

使用上述方法时,在返回符完成后,响应正文 被选中,如果未使用,则释放它以防止内存和连接泄漏。 因此,无法在下游进一步解码响应。这取决于所提供的 函数,用于声明如何在需要时解码响应。​​Mono​​​​Flux​

2.4. 请求正文

请求正文可以从由以下人员处理的任何异步类型进行编码, likeor Kotlin 协程如以下示例所示:​​ReactiveAdapterRegistry​​​​Mono​​​​Deferred​

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

或者,如果您有实际值,则可以使用快捷方式方法, 如以下示例所示:​​bodyValue​

Person person = ... ;

Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(person)
.retrieve()
.bodyToMono(Void.class);

2.4.1. 表单数据

要发送表单数据,您可以提供 aas 正文。请注意, 内容自动设置为由。以下示例演示如何使用:​​MultiValueMap<String, String>​​​​application/x-www-form-urlencoded​​​​FormHttpMessageWriter​​​​MultiValueMap<String, String>​

MultiValueMap<String, String> formData = ... ;

Mono<Void> result = client.post()
.uri("/path", id)
.bodyValue(formData)
.retrieve()
.bodyToMono(Void.class);

您还可以使用 以内联方式提供表单数据,如以下示例所示:​​BodyInserters​

Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);

2.4.2. 多部分数据

要发送多部分数据,您需要提供其值为 表示部件内容的实例或表示内容和 部件的标头。提供方便的 API 来准备 分段请求。以下示例演示如何创建:​​MultiValueMap<String, ?>​​​​Object​​​​HttpEntity​​​​MultipartBodyBuilder​​​​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();

在大多数情况下,您不必为每个部分指定。目录 类型是根据选择自动确定的,以对其进行序列化 或者,在 A 的情况下,基于文件扩展名。如有必要,您可以 通过重载之一显式为每个部分提供 to 使用 构建器方法。​​Content-Type​​​​HttpMessageWriter​​​​Resource​​​​MediaType​​​​part​

一旦准备好了,最简单的方法就是把它传递给 通过方法,如以下示例所示:​​MultiValueMap​​​​WebClient​​​​body​

MultipartBodyBuilder builder = ...;

Mono<Void> result = client.post()
.uri("/path", id)
.body(builder.build())
.retrieve()
.bodyToMono(Void.class);

如果包含至少一个非值,也可以 表示常规表单数据(即),您不需要 设置到。使用时总是如此,这确保了包装。​​MultiValueMap​​​​String​​​​application/x-www-form-urlencoded​​​​Content-Type​​​​multipart/form-data​​​​MultipartBodyBuilder​​​​HttpEntity​

作为替代方案,您还可以提供多部分内容, 内联样式,通过内置,如以下示例所示:​​MultipartBodyBuilder​​​​BodyInserters​

Mono<Void> result = client.post()
.uri("/path", id)
.body(fromMultipartData("fieldPart", "value").with("filePart", resource))
.retrieve()
.bodyToMono(Void.class);

​PartEvent​

要按顺序流式传输多部分数据,可以通过对象提供分段内容。​​PartEvent​

  • 可以通过以下方式创建表单字段。FormPartEvent::create
  • 可以通过以下方式创建文件上传。FilePartEvent::create

您可以通过以下方式连接从方法返回的流,并创建请求 这。​​Flux::concat​​​​WebClient​

例如,此示例将发布一个包含表单域和文件的多部分表单。

Resource resource = ...
Mono<String> result = webClient
.post()
.uri("https://example.com")
.body(Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
), PartEvent.class)
.retrieve()
.bodyToMono(String.class);

在服务器端,通过 aoror 接收的对象可以中继到另一个服务 通过。​​PartEvent​​​​@RequestBody​​​​ServerRequest::bodyToFlux(PartEvent.class)​​​​WebClient​

2.5. 过滤器

您可以通过 in 注册客户端过滤器 () 以拦截和修改请求,如以下示例所示:​​ExchangeFilterFunction​​​​WebClient.Builder​

WebClient client = WebClient.builder()
.filter((request, next) -> {

ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();

return next.exchange(filtered);
})
.build();

这可用于跨领域问题,例如身份验证。以下示例使用 通过静态工厂方法进行基本身份验证的筛选器:

WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "password"))
.build();

可以通过改变现有实例来添加或删除过滤器,从而产生 在不影响原始实例的新实例中。例如:​​WebClient​​​​WebClient​

WebClient client = webClient.mutate()
.filters(filterList -> {
filterList.add(0, basicAuthentication("user", "password"));
})
.build();

​WebClient​​是围绕过滤器链的薄立面,后跟一个。它提供了一个工作流程来发出请求,从更高的位置进行编码 级别对象,它有助于确保始终使用响应内容。 当过滤器以某种方式处理响应时,必须格外小心以始终消耗 其内容或以其他方式将其传播到下游这将确保 一样。下面是一个过滤器,用于处理状态代码,但确保 任何响应内容(无论是否预期)都会发布:​​ExchangeFunction​​​​WebClient​​​​UNAUTH

public ExchangeFilterFunction renewTokenFilter() {
return (request, next) -> next.exchange(request).flatMap(response -> {
if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) {
return response.releaseBody()
.then(renewToken())
.flatMap(token -> {
ClientRequest newRequest = ClientRequest.from(request).build();
return next.exchange(newRequest);
});
} else {
return Mono.just(response);
}
});
}

2.6. 属性

您可以向请求添加属性。如果您想传递信息,这很方便 通过过滤器链并影响给定请求的过滤器行为。 例如:

WebClient client = WebClient.builder()
.filter((request, next) -> {
Optional<Object> usr = request.attribute("myAttribute");
// ...
})
.build();

client.get().uri("https://example.org/")
.attribute("myAttribute", "...")
.retrieve()
.bodyToMono(Void.class);

}

请注意,您可以在级别全局配置 acallback,这允许您将属性插入到所有请求中, 例如,可以在Spring MVC应用程序中用于填充 基于数据的请求属性。​​defaultRequest​​​​WebClient.Builder​​​​ThreadLocal​

2.7. 上下文

属性提供了一种将信息传递到筛选器的便捷方法 链,但它们只影响当前请求。如果要传递以下信息 传播到嵌套的其他请求,例如通过,或在之后执行, 例如,通过,那么你需要使用反应器。​​flatMap​​​​concatMap​​​​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.8. 同步使用

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

以上只是一个例子。还有很多其他模式和运算符用于放置 一起是一个反应式管道,可以进行许多远程调用,可能有些是嵌套的, 相互依存,直到最后都不会阻塞。

举报

相关推荐

C# .NET 中的反应式系统

0 条评论