关于Spring框架(官方文档)

反应性堆栈上的Web

Version 5.1.0.RELEASE

目录

1. Spring WebFlux 

1.9.反应铁心

1.10。使用DispatcherHandler

1.11.带注释控制器

1.12.功能端点

1.13.URI链接

1.14.CORS

1.15.Web安全

1.16。视图技术

1.17.http缓存

1.18。WebFlux Config

1.19.http/2

2.WebClient

2.1.配置

2.2.使用retrieve()

2.3.使用exchange()

2.4.请求体

2.5.客户端过滤器

2.6.测试

3.WebSocket

3.1.WebSocket简介

3.2.WebSocket API

4.测试

5.反应库


文档的这一部分介绍了对构建在反应流要在非阻塞服务器上运行的API,如Netty、Under拖车和Servlet3.1+容器.个别章节涵盖 Spring WebFlux 框架,反应性WebClient,支持测试,和反应库。有关Servlet堆栈web应用程序,请参见Servlet堆栈上的Web.

1.SpringWebFlux

Spring框架中包含的原始Web框架SpringWebMVC是为ServletAPI和servlet容器构建的。反应性堆栈Web框架SpringWebFlux后来在5.0版中添加.它是完全非阻塞的,支持。反应流背压,并运行在诸如Netty、Under拖车和Servlet3.1+容器等服务器上。

这两个Web框架都反映了它们的源模块的名称(Springwebmvc和 Spring WebFlux )和在Spring框架中并行共存。每个模块都是可选的。应用程序可以使用一个或另一个模块,或者,在某些情况下,两者都使用-例如,带有反应性的SpringMVC控制器WebClient.

1.1.动机

为什么会创建SpringWebFlux?

部分答案是需要一个非阻塞的Web堆栈来处理与少量线程的并发,并使用较少的硬件资源进行扩展。Servlet3.1确实提供了一个用于非阻塞I/O的API。但是,使用它可以远离ServletAPI的其余部分,在那里契约是同步的(FilterServlet)或阻塞(getParametergetPart)这是一个新的公共API作为跨任何非阻塞运行时的基础的动机。这一点很重要,因为服务器(如Netty)是在异步、非阻塞空间中建立的。

答案的另一部分是函数式编程。就像Java 5中添加注释创造了机会(例如带注释的REST控制器或单元测试)一样,在Java 8中添加lambda表达式为Java中的函数API创造了机会。这对于非阻塞应用程序和延续式api(由CompletableFuture和反应X),允许异步逻辑的声明式组合。在编程模型级别,Java 8使SpringWebFlux能够提供功能Web端点和带注释的控制器。

1.2.定义“反应性”

我们谈到了“非阻塞”和“功能性”,但是反应是什么意思呢?

“反应性”一词指的是建立在对变化做出反应的编程模型-网络组件对I/O事件的反应、UI控制器对鼠标事件的反应等。从这个意义上说,非阻塞是反应性的,因为我们现在没有被阻塞,而是在操作完成或数据可用时对通知作出反应。

还有一个重要的机制,我们在SpringTeam中与“反应性”相关联,那就是无阻塞的背压。在同步的命令式代码中,阻塞调用是一种自然的背压形式,迫使调用方等待。在非阻塞代码中,控制事件的速度变得非常重要,这样快速生成器就不会淹没它的目标。

反应性流是小规格(也采纳在Java 9中,它定义了具有背压的异步组件之间的交互。例如,数据存储库(充当出版者)可以生成HTTP服务器(充当订户)然后可以写入响应。反应性流的主要目的是让订阅者控制发布服务器生成数据的速度或速度。

  常见问题:如果出版商不能放慢速度,怎么办?
反应流的目的只是为了建立机制和边界。如果发行者不能放慢速度,它必须决定是缓冲、删除还是失败。

1.3.反应性API

反应性流对于互操作性起着重要作用。它对库和基础设施组件很感兴趣,但作为应用程序API却不太有用,因为它的级别太低了。应用程序需要一个更高级、更丰富、功能更好的api来组成异步逻辑-类似于java 8。StreamAPI,但不只是用于集合。这就是反应性库所扮演的角色。

反应器是SpringWebFlux的首选反应库。它提供了MonoFlux用于处理0.1的数据序列的API类型(Mono)和0.N(Flux)通过与ReactiveX对齐的一组丰富的运算符运算符词汇。反应堆是一个反应流库,因此它的所有操作人员都支持无阻塞的反压力.反应堆非常关注服务器端的Java。它是与Spring密切合作开发的。

WebFlux需要将反应堆作为核心依赖项,但它可以通过反应性流与其他反应性库进行互操作。作为一般规则,WebFluxAPI接受普通的Publisher作为输入,在内部将其调整为反应堆类型,并使用该类型,并返回Flux或者是Mono作为输出。所以,你可以通过任何Publisher作为输入,您可以对输出应用操作,但是您需要调整输出,以便与另一个反应性库一起使用。只要可行(例如,带注释的控制器),WebFlux就会透明地适应RxJava或其他反应性库的使用。看见反应库更多细节。

1.4.编程模型

这,这个,那,那个spring-web模块包含支持SpringWebFlux的反应性基础,包括HTTP抽象、反应性流适配器对于支持的服务器,编解码器,还有一个核心使用WebHandlerAPI可与ServletAPI相媲美,但具有非阻塞契约。

在此基础上,SpringWebFlux提供了两种编程模型的选择:

  • 带注释控制器:与SpringMVC一致,并基于来自spring-web模块。SpringMVC和WebFlux控制器都支持反应性(反应堆和RxJava)返回类型,因此很难区分它们。一个值得注意的区别是WebFlux也支持反应性。@RequestBody争论。

  • 功能端点基于Lambda的轻量级函数编程模型.您可以将其看作是一个小库或一组实用程序,应用程序可以使用它们来路由和处理请求。与带注释的控制器的最大区别是,应用程序负责从开始到结束的请求处理,而不是通过注释和被调用来声明意图。

1.5.适用性

SpringMVC还是WebFlux?

一个自然要问的问题,但一个不健全的二分法。实际上,两者共同努力扩大可用选项的范围。这两者是为了彼此的连续性和一致性而设计的,它们可以并行不悖,来自双方的反馈对双方都有好处。下图显示了两者之间是如何联系的,它们有什么共同之处,以及它们各自唯一支持的是什么:

我们建议你考虑以下几点:

  • 如果您有一个工作良好的SpringMVC应用程序,则不需要进行更改。命令式编程是编写、理解和调试代码的最简单方法。您可以最大限度地选择库,因为历史上大多数库都是阻塞的。

  • 如果您已经在购买无阻塞的Web堆栈,SpringWebFlux提供了与此领域中其他组件相同的执行模型,并且还提供了服务器的选择(Netty、Tomcat、Jetty、Under拖车和Servlet 3.1+容器)、编程模型的选择(带注释的控制器和功能Web端点),以及反应库的选择(反应堆、RxJava或其他)。

  • 如果您对用于Java 8 lambdas或Kotlin的轻量级功能性Web框架感兴趣,则可以使用SpringWebFlux功能Web端点。对于需求不那么复杂的小型应用程序或微服务来说,这也是一个很好的选择,可以从更大的透明度和控制中受益。

  • 在微服务体系结构中,可以使用SpringMVC或SpringWebFlux控制器或SpringWebFlux功能端点混合应用程序。在两个框架中都支持相同的基于注释的编程模型,这样可以更容易地重用知识,同时也可以为正确的工作选择合适的工具。

  • 评估应用程序的一种简单方法是检查其依赖项。如果您需要使用阻塞持久性API(JPA、JDBC)或网络API,那么SpringMVC至少是通用体系结构的最佳选择。在一个单独的线程上执行阻塞调用在技术上是可行的,但您不能充分利用一个非阻塞的Web堆栈。

  • 如果您有一个带有远程服务调用的SpringMVC应用程序,请尝试使用反应性WebClient。您可以返回反应性类型(反应堆、RxJava、或其他)直接来自SpringMVC控制器方法。每次调用的延迟或呼叫之间的相互依赖越大,好处就越大。SpringMVC控制器也可以调用其他反应性组件。

  • 如果您有一个庞大的团队,请记住在转向非阻塞、功能和声明性编程过程中的陡峭学习曲线。一种不需要完全开关即可启动的实用方法是使用反应性。WebClient。除此之外,从小事做起,衡量利益。我们预计,对于广泛的应用程序,这一转变是不必要的。如果您不确定该寻找什么好处,那么首先了解非阻塞I/O是如何工作的(例如,单线程Node.js上的并发性)及其效果。

1.6.服务器

SpringWebFlux支持Tomcat、Jetty、Servlet3.1+容器,以及Netty和Undertext等非servlet运行时。所有服务器都适应低级别,公共API所以更高层次的人编程模型可以跨服务器支持。

SpringWebFlux没有内置支持来启动或停止服务器。然而,很容易组装Spring配置和WebFlux基础设施和运行它有几行代码。

SpringBoot有一个WebFlux启动程序,可以自动执行这些步骤。默认情况下,初学者使用Netty,但通过更改Maven或Gradle依赖项,很容易切换到Tomcat、Jetty或Under拖车。SpringBoot默认为Netty,因为它在异步、非阻塞空间中被更广泛地使用,并且允许客户机和服务器共享资源。

Tomcat和Jetty可以与SpringMVC和WebFlux一起使用。但是,请记住,它们的使用方式是非常不同的。SpringMVC依赖Servlet阻塞I/O,并允许应用程序在需要时直接使用ServletAPI。SpringWebFlux依赖Servlet3.1无阻塞I/O,并且在低级别适配器后面使用ServletAPI,并且不公开供直接使用。

对于下面,SpringWebFlux直接使用没有ServletAPI的潜API。

1.7.性能与规模

表演具有许多特点和意义。反应和非阻塞通常不会使应用程序运行得更快。在某些情况下,它们可以(例如,如果使用WebClient并行执行远程调用)。总的来说,它需要更多的工作来完成非阻塞方式,这可以稍微增加所需的处理时间。

反应性和非阻塞性的关键预期好处是能够以较小的、固定的线程数和较少的内存进行扩展。这使得应用程序在负载下更具弹性,因为它们以更可预测的方式扩展。然而,为了观察这些好处,您需要有一些延迟(包括缓慢和不可预测的网络I/O的混合)。这就是反应堆栈开始显示其优势的地方,差异可能是巨大的。

1.8.并发模型

SpringMVC和SpringWebFlux都支持带注释的控制器,但是在并发模型和阻塞和线程的默认假设方面有一个关键的区别。

在SpringMVC(以及一般的servlet应用程序)中,假设应用程序可以阻塞当前线程(例如,远程调用),因此servlet容器在请求处理期间使用一个大型线程池来吸收潜在的阻塞。

在SpringWebFlux(和一般的非阻塞服务器)中,假定应用程序不阻塞,因此,非阻塞服务器使用一个小的、固定大小的线程池(事件循环工作人员)来处理请求。

  “缩放”和“少量线程”听起来可能自相矛盾,但永远不要阻止当前线程(并且依赖回调)意味着您不需要额外的线程,因为没有阻塞调用来吸收。

调用阻塞API

如果您确实需要使用阻塞库呢?反应堆和RxJava都提供了publishOn运算符继续在其他线程上进行处理。这意味着有一个容易逃生的舱口。但是,请记住,阻塞API并不适合这种并发模型。

可变状态

在反应堆和RxJava中,您通过操作符声明逻辑,并且在运行时形成一个反应管道,其中数据按顺序处理,处于不同的阶段。这样做的一个主要好处是,它使应用程序不必保护可变状态,因为该管道中的应用程序代码从未被并发调用。

线程模型

在使用SpringWebFlux运行的服务器上应该看到哪些线程?

  • 在“普通”SpringWebFlux服务器(例如,没有数据访问或其他可选依赖项)上,您可以期望服务器有一个线程,还有几个线程用于请求处理(通常是CPU内核的数量)。但是,servlet容器可以从更多的线程(例如Tomcat上的10个线程)开始,以支持servlet(阻塞)I/O和Servlet3.1(非阻塞)I/O的使用。

  • 反应性WebClient以事件循环样式操作。因此,您可以看到与此相关的少量固定数量的处理线程(例如,reactor-http-nio-使用反应堆Netty连接器)。但是,如果反应堆Netty同时用于客户端和服务器,则默认情况下两个共享事件循环资源。

  • 反应器和RxJava提供线程池抽象,称为调度器,用于publishOn运算符,用于将处理切换到不同的线程池。调度程序的名称表示特定的并发策略-例如,“并行”(用于数量有限的线程与CPU绑定的工作)或“弹性”(用于大量线程的I/O绑定工作)。如果您看到这样的线程,这意味着一些代码正在使用特定的线程池。Scheduler战略。

  • 数据访问库和其他第三方依赖项也可以创建和使用自己的线程。

配置

Spring框架不支持启动和停止服务器。要为服务器配置线程模型,需要使用特定于服务器的配置API,或者,如果使用SpringBoot,则检查每个服务器的SpringBoot配置选项。你可以的。配置这,这个,那,那个WebClient直接。有关所有其他库,请参阅它们各自的文档。

1.9.反应铁心

这,这个,那,那个spring-web模块包含构建反应性Web应用程序的抽象和基础结构。对于服务器端处理,这分为两个不同的级别:

  • HttpHandler:基本的、通用的HTTP请求处理API,具有非阻塞I/O和(反应性流)背压,以及每个受支持的服务器的适配器。

  • 使用WebHandlerAPI:服务器请求处理的API级别稍高,但仍然是通用的,这是高级编程模型(如带注释的控制器和功能端点)的基础。

反应核还包括编解码器供客户端和服务器端使用。

1.9.1.使用HttpHandler

HttpHandler是一个使用单一方法处理请求和响应的简单契约。它是有意最小化的,因为它的主要目的是为HTTP请求处理提供对不同服务器API的抽象。

下表描述了支持的服务器API:

服务器名称 服务器API 反应流支持

奈蒂

Netty API

反应堆Netty

下引

下拖曳API

弹簧网:向下拖曳到反应流桥

猫猫

Servlet3.1非阻塞I/O;TomcatAPI来读取和写入ByteBuffers与字节[]

Springweb:Servlet3.1无阻塞I/O到反应性流桥

码头

Servlet3.1非阻塞I/O;JettyAPI来编写ByteBuffersvs字节[]

Springweb:Servlet3.1无阻塞I/O到反应性流桥

Servlet3.1容器

Servlet3.1非阻塞I/O

Springweb:Servlet3.1无阻塞I/O到反应性流桥

下表描述服务器依赖项(和支持的版本):

服务器名称 群id 工件名称

反应堆Netty

io.projectreactor.netty

反应堆网

下引

io.undertow

下牵引芯

猫猫

org.apache.tomcat.embed

Tomcat-嵌入核

码头

org.eclipse.jetty

Jetty-server,jetty-servlet

下面的代码片段适应HttpHandler对于每个服务器API:

反应堆Netty

HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();

下引

HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();

猫猫

HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);

Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();

码头

HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);

Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();

ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();

Servlet 3.1+容器

要将WAR部署到任何Servlet3.1+容器,可以扩展并包括AbstractReactiveWebInitializer在战争中。那个类封装了一个HttpHandler带着ServletHttpHandlerAdapter并将其注册为Servlet.

1.9.2.使用WebHandlerAPI

WebHandler api是一个通用的服务器web api,用于通过WebExceptionHandlerWebFilter组件和目标WebHandler组件。你可以用WebHttpHandlerBuilder通过向构建器添加组件或从Spring中检测组件ApplicationContext。生成器返回使用HttpHandler然后,您可以使用它在任何受支持的服务器上运行。

HttpHandler目标是跨HTTP服务器的最小契约,WebHandlerAPI提供了通常用于构建Web应用程序的基本特性。例如,ServerWebExchangeWebHandler API组件不仅提供了对请求和响应的访问,还提供了对请求和会话属性的访问,以及对解析的表单数据、多部分数据等的访问。

特殊豆类

下表列出了WebHttpHandlerBuilder侦测:

豆名 豆型 数数 描述

WebExceptionHandler

0..N

链中的异常提供处理。WebFilter实例和目标WebHandler。有关详细信息,请参阅例外.

WebFilter

0..N

将拦截样式逻辑应用于过滤链其余部分和目标的前后。WebHandler。有关详细信息,请参阅滤光片.

webHandler

WebHandler

1

请求的处理程序。

webSessionManager

WebSessionManager

0..1

经理WebSession的方法公开的实例。ServerWebExchange.DefaultWebSessionManager默认情况下。

serverCodecConfigurer

ServerCodecConfigurer

0..1

为获取HttpMessageReader实例来解析表单数据和多部分数据,然后通过ServerWebExchangeServerCodecConfigurer.create()默认情况下。

localeContextResolver

LocaleContextResolver

0..1

解析器LocaleContext通过一种方法暴露在ServerWebExchange.AcceptHeaderLocaleContextResolver默认情况下。

forwardedHeaderTransformer

ForwardedHeaderTransformer

0..1

对于处理转发的类型标头,可以提取和删除它们,或者只删除它们。默认情况下不使用。

表格数据

ServerWebExchange公开以下访问表单数据的方法:

Mono> getFormData();

这,这个,那,那个DefaultServerWebExchange使用配置的HttpMessageReader解析表单数据(application/x-www-form-urlencoded)变成MultiValueMap。默认情况下,FormHttpMessageReader配置为由ServerCodecConfigurerbean(参见WebHandler API).

多部分数据

与SpringMVC相同

ServerWebExchange公开以下访问多部分数据的方法:

Mono> getMultipartData();

这,这个,那,那个DefaultServerWebExchange使用配置的HttpMessageReader>解析multipart/form-data内容转化为MultiValueMap。目前,同步NIO多部分是唯一支持第三方库的库,也是我们所知道的用于多部分请求的非阻塞解析的惟一库。通过ServerCodecConfigurerbean(参见WebHandler API).

若要以流方式解析多部分数据,可以使用Flux从一个HttpMessageReader相反。例如,在带注释的控制器中,使用@RequestPart暗示Map-类似于按名称访问各个部分,因此需要对多部分数据进行完整解析。相反,您可以使用@RequestBody将内容解码为Flux没有收集到MultiValueMap.

转发标头

与SpringMVC相同

当请求通过代理(例如负载平衡器)时,主机、端口和方案可能会发生变化,因此从客户端的角度来看,创建指向正确主机、端口和方案的链接是一个挑战。

RFC 7239定义ForwardedHTTP头,代理可以用来提供有关原始请求的信息。还有其他非标准的标头,包括X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-Ssl,和X-Forwarded-Prefix.

ForwardedHeaderTransformer是一个组件,它根据转发的标头修改请求的主机、端口和方案,然后删除这些标头。您可以将其声明为名称为forwardedHeaderTransformer,而且是检出用过了。

转发的标头需要考虑安全性,因为应用程序无法知道标头是由代理添加的,还是由恶意客户端添加的。这就是为什么应该将信任边界上的代理配置为删除来自外部的不受信任的转发通信量。您还可以配置ForwardedHeaderTransformer带着removeOnly=true,在这种情况下,它移除但不使用标头。

  5.1ForwardedHeaderFilter不受欢迎并被ForwardedHeaderTransformer因此,在创建交换之前,可以更早地处理转发的标头。如果无论如何配置了筛选器,则将其从筛选器列表中删除,并且ForwardedHeaderTransformer取而代之的是。

1.9.3.滤光片

与SpringMVC相同

在.。使用WebHandlerAPI,您可以使用WebFilter在过滤器和目标处理链的其余部分之前和之后应用拦截样式逻辑。WebHandler。当使用WebFlux Config,注册WebFilter简单到将其声明为Springbean,并(可选)使用@Order关于bean声明或通过实现Ordered.

CORS

与SpringMVC相同

SpringWebFlux通过控制器上的注释为CORS配置提供了细粒度的支持。但是,当您将它与SpringSecurity一起使用时,我们建议依赖内置的CorsFilter,必须在Spring Security的过滤器链之前订购。

见CORS而CORSWebFilter更多细节。

1.9.4.例外

与SpringMVC相同

在.。使用WebHandlerAPI,您可以使用WebExceptionHandler的链中的异常。WebFilter实例和目标WebHandler。当使用WebFlux Config,注册WebExceptionHandler简单到将其声明为Springbean,并(可选)使用@Order关于bean声明或通过实现Ordered.

下表描述了可用的WebExceptionHandler实现:

异常处理程序 描述

ResponseStatusExceptionHandler

提供对类型异常的处理。ResponseStatusException通过设置异常的HTTP状态代码的响应。

WebFluxResponseStatusExceptionHandler

延展ResponseStatusExceptionHandler的HTTP状态代码。@ResponseStatus任何异常的注释。

属性中声明此处理程序。WebFlux Config.

1.9.5.编解码器

与SpringMVC相同

HttpMessageReaderHttpMessageWriter是编码和解码HTTP请求和响应内容的契约,通过非阻塞I/O与(有效率的流)背压。

EncoderDecoder是编码和解码内容的契约,独立于HTTP。它们可以用EncoderHttpMessageWriterDecoderHttpMessageReader并用于Web处理。

所有编解码器都是客户端或服务器端使用的。都是建立在DataBuffer,它抽象字节缓冲区表示,例如NettyByteBufjava.nio.ByteBuffer(见数据缓冲区和编解码器了解更多细节)。ClientCodecConfigurerServerCodecConfigurer通常用于配置和自定义要在应用程序中使用的编解码器。

这,这个,那,那个spring-core模块有编码器和解码器byte[]ByteBufferDataBufferResource,和String。这,这个,那,那个spring-web模块为Jackson JSON、Jackson Say、JAXB 2、协议缓冲区和其他特定于Web的HTTP消息读取器和作者添加用于表单数据、多部分请求和服务器发送事件的编码器和解码器。

使用Jackson

解码器依赖于Jackson的非阻塞字节数组解析器将字节块流解析为TokenBuffer流,然后可以将其转换为带有Jackson‘s的对象。ObjectMapper。JSON和微笑当前支持(二进制JSON)数据格式。

编码器处理Publisher,如下:

  • 如果PublisherMono(即单个值),该值在可用时被编码。

  • 如果媒体类型是application/stream+json对于JSON或application/stream+x-jackson-smile对于微笑来说,每个价值都是由Publisher是单独编码的(后面跟着JSON中的新行)。

  • 否则,所有来自Publisher聚集在一起Flux#collectToList(),并将结果集合编码为数组。

作为上述规则的特例,ServerSentEventHttpMessageWriter从其输入中发出的提要项Publisher单独进入Jackson2JsonEncoder作为Mono.

注意,Jackson JSON编码器和解码器都显式地退出了呈现类型的元素String。相反String实例被视为低级内容(即序列化JSON),并由CharSequenceEncoder。如果你想Flux作为JSON数组呈现,您必须使用Flux#collectToList()并提供Mono>相反。

http流

与SpringMVC相同

当多值反应性类型(如Flux用于响应呈现,则可以将其收集到List并呈现为一个整体(例如,一个JSON数组),或者将其视为一个无限流,每个项都会立即刷新。根据内容协商和选定的媒体类型确定其中的内容,这可能意味着流格式(例如,text/event-streamapplication/stream+json)或否(例如,application/json).

当流到HTTP响应时,不管媒体类型如何(例如,text/event-streamapplication/stream+json),定期发送数据是很重要的,因为如果客户端断开连接,写入就会失败。发送可以采取空(仅限注释)SSE事件的形式,或者另一方必须将其解释为心跳和忽略的任何其他数据。

1.9.6.测井

与SpringMVC相同

SpringWebFlux中的调试级别日志记录设计为紧凑、最小和人性化。它将重点放在一次又一次有用的高价值信息比特上,而其他信息只在调试特定问题时才有用。

跟踪级别日志通常遵循与调试相同的原则(例如,也不应该是消防软管),但可以用于调试任何问题。此外,在跟踪和调试时,一些日志消息可能会显示不同级别的详细信息。

良好的日志记录来自于使用日志的经验。如果你发现任何不符合规定的目标,请告诉我们。

日志ID

在WebFlux中,可以在多个线程上执行单个请求,而线程ID对于关联属于特定请求的日志消息并不有用。这就是为什么WebFlux日志消息在默认情况下以特定于请求的ID作为前缀的原因。

在服务器端,日志ID存储在ServerWebExchange属性(LOG_ID_ATTRIBUTE),而基于该ID的完全格式化前缀可从ServerWebExchange#getLogPrefix()。在WebClient侧,日志ID存储在ClientRequest属性(LOG_ID_ATTRIBUTE),而完全格式化的前缀可从ClientRequest#logPrefix().

测井敏感数据

与SpringMVC相同

DEBUGTRACE日志可以记录敏感信息。这就是表单参数和标头在默认情况下被屏蔽的原因,并且必须显式地启用它们的全部日志记录。

下面的示例演示了如何针对服务器端请求这样做:

@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().enableLoggingRequestDetails(true);
    }
}

下面的示例演示如何对客户端请求执行此操作:

Consumer consumer = configurer ->
        configurer.defaultCodecs().enableLoggingRequestDetails(true);

WebClient webClient = WebClient.builder()
        .exchangeStrategies(ExchangeStrategies.builder().codecs(consumer).build())
        .build();

1.10。使用DispatcherHandler

与SpringMVC相同

SpringWebFlux,类似SpringMVC,是围绕前端控制器模式设计的,其中一个中心WebHandlerDispatcherHandler,为请求处理提供共享算法,而实际工作则由可配置的委托组件执行。该模型具有灵活性,支持多种工作流。

DispatcherHandler从Spring配置中发现它需要的委托组件。它也被设计成Springbean本身并实现ApplicationContextAware以访问其运行的上下文。如果DispatcherHandler的bean名称声明为webHandler,反过来,它是由WebHttpHandlerBuilder,它将一个请求处理链组合在一起,如使用WebHandlerAPI.

WebFlux应用程序中的Spring配置通常包含:

  • DispatcherHandler用豆子的名字,webHandler

  • WebFilterWebExceptionHandler豆子

  • DispatcherHandler特殊豆类

  • 其他

配置给WebHttpHandlerBuilder要构建处理链,如下例所示:

ApplicationContext context = ...
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context);

结果HttpHandler已准备好与服务器适配器.

1.10.1.特殊豆类

与SpringMVC相同

这,这个,那,那个DispatcherHandler委托特殊bean处理请求并呈现适当的响应。我们所说的“特殊bean”指的是Spring管理的Object实现WebFlux框架契约的实例。这些通常带有内置契约,但您可以自定义它们的属性、扩展它们或替换它们。

下表列出了DispatcherHandler。请注意,在较低级别上还检测到了其他bean(请参见特殊豆类在WebHandler API中)。

豆型 解释

HandlerMapping

将请求映射到处理程序。映射是基于一些标准,其细节取决于HandlerMapping实现-带注释的控制器、简单的URL模式映射等。

HandlerMapping实现是RequestMappingHandlerMapping@RequestMapping附加说明的方法,RouterFunctionMapping对于功能性端点路由,以及SimpleUrlHandlerMapping对于URI路径模式的显式注册和WebHandler实例。

HandlerAdapter

帮助DispatcherHandler调用映射到请求的处理程序,而不管处理程序是如何实际调用的。例如,调用带注释的控制器需要解析注释。a的主要目的HandlerAdapter就是保护DispatcherHandler从这些细节。

HandlerResultHandler

处理程序调用的结果并最后确定响应。看见结果处理.

1.10.2.WebFlux Config

与SpringMVC相同

应用程序可以声明基础设施bean(在WebHandler API和DispatcherHandler)处理请求所需的。但是,在大多数情况下,WebFlux Config是最好的起点。它声明了所需的bean,并提供了一个更高级别的配置回调API来对其进行自定义。

  SpringBoot依赖于WebFlux配置来配置SpringWebFlux,并且还提供了许多额外方便的选项。

1.10.3.加工

与SpringMVC相同

DispatcherHandler处理请求如下:

  • HandlerMapping请求查找匹配的处理程序,并使用第一个匹配项。

  • 如果找到处理程序,则通过适当的HandlerAdapter,它将执行过程中的返回值公开为HandlerResult.

  • 这,这个,那,那个HandlerResult给予适当的HandlerResultHandler通过直接写入响应或使用视图呈现来完成处理。

1.10.4。结果处理

处理程序调用的返回值,通过HandlerAdapter,被包装为HandlerResult,以及一些附加的上下文,并传递给第一个HandlerResultHandler声称对此表示支持。下表显示可用的HandlerResultHandler实现,所有这些实现都在WebFlux Config:

结果处理器类型 返回值 默认命令

ResponseEntityResultHandler

ResponseEntity,通常来自@Controller实例。

0

ServerResponseResultHandler

ServerResponse,通常来自功能端点。

0

ResponseBodyResultHandler

句柄返回值@ResponseBody方法或@RestController上课。

100

ViewResolutionResultHandler

CharSequenceView, 模型, Map, 渲染,或任何其他Object被视为模型属性。

另见视图解析.

Integer.MAX_VALUE

1.10.5.例外

与SpringMVC相同

这,这个,那,那个HandlerResultHandlerAdapter可以根据特定于处理程序的机制公开用于错误处理的函数。如果以下情况下调用此错误函数:

  • 处理程序(例如,@Controller)调用失败。

  • 处理程序返回值的过程。HandlerResultHandler失败了。

错误函数可以更改响应(例如,错误状态),只要错误信号发生在从处理程序返回的响应类型生成任何数据项之前。

这就是为什么@ExceptionHandler方法@Controller支持类。相比之下,SpringMVC中同样的支持是建立在HandlerExceptionResolver。一般来说,这并不重要。但是,请记住,在WebFlux中,不能使用@ControllerAdvice若要处理在选择处理程序之前发生的异常,请执行以下操作。

另见管理异常在“附加说明的主计长”一节中,或例外在WebHandler API部分中。

1.10.6.视图解析

与SpringMVC相同

视图解析允许使用HTML模板和模型向浏览器呈现,而不需要将您绑定到特定的视图技术。在SpringWebFlux中,通过专用的HandlerResultHandler用ViewResolver实例将字符串(表示逻辑视图名称)映射到View举个例子。这,这个,那,那个View然后用于呈现响应。

装卸

与SpringMVC相同

这,这个,那,那个HandlerResult传入ViewResolutionResultHandler包含来自处理程序的返回值和包含在请求处理过程中添加的属性的模型。返回值作为下列之一处理:

  • StringCharSequence*要解析为View通过配置的列表ViewResolver实现。

  • void:根据请求路径选择默认视图名称,减去前导斜杠和尾斜杠,并将其解析为View。当未提供视图名称(例如,返回模型属性)或异步返回值(例如,Mono已完成)。

  • 渲染用于视图解析场景的API。使用代码完成来探索IDE中的选项。

  • ModelMap要添加到请求的模型中的额外模型属性。

  • 任何其他:任何其他返回值(简单类型除外,由BeanUtils#isSimpleProperty)被视为要添加到模型中的模型属性。属性名是从类名派生的。公约,除非处理程序方法@ModelAttribute注释是存在的。

模型可以包含异步的、反应性的类型(例如,来自反应堆或RxJava)。在渲染之前,AbstractView将这些模型属性解析为具体的值并更新模型。单值反应性类型解析为单个值或无值(如果为空),而多值反应性类型(例如,Flux)被收集并解析为List.

要配置视图解析,只需添加ViewResolutionResultHandlerbean到您的Spring配置。WebFlux Config提供用于视图解析的专用配置API。

看见视图技术有关与SpringWebFlux集成的视图技术的更多信息。

重定向

与SpringMVC相同

特辑redirect:视图名称中的前缀允许您执行重定向。这,这个,那,那个UrlBasedViewResolver(和子类)将此识别为需要重定向的指令。视图名称的其余部分是重定向URL。

净效果与控制器返回的RedirectViewRendering.redirectTo("abc").build(),但现在控制器本身可以根据逻辑视图名称进行操作。视图名,如redirect:/some/resource相对于当前应用程序,而视图名(如redirect:http://example.com/arbitrary/path重定向到绝对URL。

内容协商

与SpringMVC相同

ViewResolutionResultHandler支持内容协商。它将请求媒体类型与每个选定的媒体类型进行比较。View。第一View它支持请求的媒体类型。

为了支持诸如JSON和XML等媒体类型,SpringWebFlux提供了HttpMessageWriterView,这是一个特别的View通过HttpMessageWriter。通常,您可以通过WebFlux配置。如果默认视图与请求的媒体类型匹配,则始终选择并使用它们。

1.11.带注释控制器

与SpringMVC相同

SpringWebFlux提供了一个基于注释的编程模型,其中@Controller@RestController组件使用注释来表示请求映射、请求输入、处理异常等等。带注释的控制器具有灵活的方法签名,不需要扩展基类或实现特定的接口。

下面的清单显示了一个基本示例:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String handle() {
        return "Hello WebFlux";
    }
}

在前面的示例中,该方法返回String写到响应体。

1.11.1.使用@Controller

与SpringMVC相同

您可以通过使用标准Springbean定义来定义控制器bean。这,这个,那,那个@Controller原型允许自动检测,并与Spring通用的检测支持对齐。@Component类路径中的类和它们的自动注册bean定义。它还充当带注释的类的原型,表明它作为Web组件的角色。

若要启用自动检测以下内容,请执行以下操作:@Controllerbean,您可以将组件扫描添加到Java配置中,如下面的示例所示:

@Configuration
@ComponentScan("org.example.web") 
public class WebConfig {

    // ...
}
  扫描org.example.web包裹。

@RestController是组合注释这本身就是元注释@Controller@ResponseBody,指示每个方法继承类型级别的控制器。@ResponseBody注释,因此,直接写入响应体,而不是视图解析,并使用HTML模板进行呈现。

1.11.2.请求映射

与SpringMVC相同

这,这个,那,那个@RequestMapping注释用于将请求映射到控制器方法。它通过URL、HTTP方法、请求参数、标头和媒体类型来匹配各种属性。您可以在类级别使用它来表示共享映射,也可以在方法级别使用它来缩小到特定端点映射。

还有HTTP方法特定的快捷变体@RequestMapping:

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

前面的注释是自定义注释这是因为,可以说,大多数控制器方法应该映射到特定的HTTP方法,而不是使用@RequestMapping,默认情况下,它与所有HTTP方法匹配。同时,@RequestMapping仍然需要在类级别表示共享映射。

下面的示例使用类型和方法级别的映射:

@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}

URI模式

与SpringMVC相同

您可以通过使用GLOB模式和通配符来映射请求:

  • ?匹配一个字符

  • *匹配路径段中的零或多个字符。

  • **匹配零或多个路径段

您还可以声明URI变量并使用@PathVariable,如以下示例所示:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}

可以在类和方法级别声明URI变量,如下例所示:

@Controller
@RequestMapping("/owners/{ownerId}") 
public class OwnerController {

    @GetMapping("/pets/{petId}") 
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}
  类级URI映射。
  方法级URI映射。

URI变量自动转换为适当的类型或TypeMismatchException已经长大了。简单类型(intlongDate默认情况下支持等等,您可以注册对任何其他数据类型的支持。看见类型转换和使用DataBinder.

URI变量可以显式命名(例如,@PathVariable("customId")),但如果名称相同,则可以忽略该细节,并使用调试信息或-parametersJava 8上的编译器标志。

语法{*varName}声明与零或多个剩余路径段匹配的URI变量。例如/resources/{*path}匹配所有文件/resources/"path"变量捕获完整的相对路径。

语法{varName:regex}声明具有以下语法的正则表达式的URI变量:{varName:regex}。例如,给定一个URL/spring-web-3.0.5 .jar,下面的方法提取名称、版本和文件扩展名:

@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
    // ...
}

URI路径模式也可以嵌入${…​}在启动时通过以下方式解析的占位符:PropertyPlaceHolderConfigurer针对本地、系统、环境和其他属性源。例如,您可以使用它来根据某些外部配置来参数化基本URL。

  SpringWebFlux使用PathPatternPathPatternParser对于URI路径匹配支持。两个类都位于spring-web并明确设计用于在运行时匹配大量URI路径模式的Web应用程序中使用HTTPURL路径。

SpringWebFlux不支持后缀模式匹配-与SpringMVC不同,在SpringMVC中,映射(如/person也匹配到/person.*。对于基于URL的内容协商,如果需要的话,我们建议使用一个查询参数,它更简单、更明确,并且不容易受到基于URL路径的攻击。

模式比较

与SpringMVC相同

当多个模式匹配一个URL时,必须对它们进行比较,以找到最佳匹配。这件事已经结束了PathPattern.SPECIFICITY_COMPARATOR寻找更具体的模式。

对于每个模式,根据URI变量和通配符的数量计算分数,其中URI变量的得分低于通配符。总分较低的模式获胜。如果两个模式的得分相同,则选择的时间越长。

所有模式(例如,**{*varName})被排除在得分之外,并且总是被排在最后。如果两种模式都是一劳永逸的,那么选择的时间就越长。

耗材媒体类型

与SpringMVC相同

属性来缩小请求映射范围。Content-Type如以下示例所示:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
    // ...
}

USPEES属性还支持否定表达式-例如,!text/plain指除text/plain.

您可以声明一个共享consumes属性在类级别。但是,与大多数其他请求映射属性不同的是,当在类级别上使用时,方法级别是consumes属性重写而不是扩展类级声明。

  MediaType为常用媒体类型提供常量-例如,APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE.

可生产介质类型

与SpringMVC相同

属性来缩小请求映射范围。Accept请求标头和控制器方法生成的内容类型列表,如下例所示:

@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

媒体类型可以指定字符集。支持否定式-例如,!text/plain指除text/plain.

  对于JSON内容类型,应该指定UTF-8charset即使RFC 7159明确指出“没有为此注册定义字符集参数”,因为有些浏览器要求它正确地解释UTF-8特殊字符。

您可以声明一个共享produces属性在类级别。但是,与大多数其他请求映射属性不同的是,当在类级别上使用时,方法级别是produces属性重写而不是扩展类级别声明。

  MediaType为常用的媒体类型提供常量。APPLICATION_JSON_UTF8_VALUEAPPLICATION_XML_VALUE.

参数和标头

与SpringMVC相同

可以根据查询参数条件缩小请求映射。您可以测试查询参数是否存在(myParam),因为它没有(!myParam),或特定值(myParam=myValue)以下示例测试具有值的参数:

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}
  检查一下myParam等号myValue.

您也可以对请求头条件使用相同的条件,如下面的示例所示:

@GetMapping(path = "/pets", headers = "myHeader=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}
  检查一下myHeader等号myValue.

http头,选项

与SpringMVC相同

@GetMapping@RequestMapping(method=HttpMethod.GET)为请求映射目的透明地支持HTTP头。控制器方法不需要改变。应用于HttpHandler服务器适配器,确保Content-Length标头设置为写入的字节数,而不实际写入响应。

默认情况下,HTTP选项是通过设置Allow中列出的HTTP方法列表的响应头@RequestMapping方法与URL模式匹配。

为了@RequestMapping如果没有HTTP方法声明,则Allow标头设置为GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS。控制器方法应该始终声明受支持的HTTP方法(例如,通过使用HTTP方法特定的变体-@GetMapping@PostMapping,以及其他)。

可以显式映射@RequestMapping方法到HTTPHead和HTTP选项,但在普通情况下这是不必要的。

自定义注释

与SpringMVC相同

SpringWebFlux支持使用组合注释用于请求映射。这些注释本身就是元注释@RequestMapping的子集(或全部)。@RequestMapping具有更窄、更具体目的属性。

@GetMapping@PostMapping@PutMapping@DeleteMapping,和@PatchMapping是组合注释的示例。它们是提供的,因为可以说,大多数控制器方法应该映射到特定的HTTP方法,而不是使用@RequestMapping,默认情况下,它与所有HTTP方法匹配。如果您需要一个组合注释的示例,请查看这些注释是如何声明的。

SpringWebFlux还支持具有自定义请求匹配逻辑的自定义请求映射属性。这是一个更高级的选项,需要子类。RequestMappingHandlerMapping和凌驾于getCustomMethodCondition方法,可以在其中检查自定义属性并返回自己的属性。RequestCondition.

显式注册

与SpringMVC相同

您可以编程方式注册Handler方法,这些方法可用于动态注册或高级情况,例如同一处理程序在不同URL下的不同实例。下面的示例演示如何做到这一点:

@Configuration
public class MyConfig {

    @Autowired
    public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) 
            throws NoSuchMethodException {

        RequestMappingInfo info = RequestMappingInfo
                .paths("/user/{id}").methods(RequestMethod.GET).build(); 

        Method method = UserHandler.class.getMethod("getUser", Long.class); 

        mapping.registerMapping(info, handler, method); 
    }

}
  注入目标处理程序和控制器的处理程序映射。
  准备请求映射元数据。
  获取处理程序方法。
  加上注册。

1.11.3。处理方法

与SpringMVC相同

@RequestMapping处理程序方法具有灵活的签名,可以从支持的控制器方法参数和返回值中进行选择。

方法参数

与SpringMVC相同

下表显示了支持的控制器方法参数。

反应类型(反应器,RxJava,或其他)在需要解析阻塞I/O(例如,读取请求体)的参数上支持)。这在Description列中被标记。对于不需要阻塞的参数,不需要反应类型。

JDK 1.8java.util.Optional作为方法参数与具有required属性(例如,@RequestParam@RequestHeader,以及其他的),并且相当于required=false.

控制器方法参数 描述

ServerWebExchange

获得全部ServerWebExchange-HTTP请求和响应、请求和会话属性的容器,checkNotModified方法等等。

ServerHttpRequestServerHttpResponse

访问HTTP请求或响应。

WebSession

进入会议。除非添加属性,否则不会强制开始新会话。支持反应性类型。

java.security.Principal

当前通过身份验证的用户-可能是特定的Principal实现类(如果已知的话)。支持反应性类型。

org.springframework.http.HttpMethod

请求的HTTP方法。

java.util.Locale

当前请求区域设置,由最特定的LocaleResolver可用-实际上,配置的LocaleResolver/LocaleContextResolver.

java.util.TimeZone + java.time.ZoneId

与当前请求关联的时区,由LocaleContextResolver.

@PathVariable

用于访问URI模板变量。看见URI模式.

@MatrixVariable

用于访问URI路径段中的名称-值对。看见矩阵变量.

@RequestParam

用于访问servlet请求参数。参数值转换为声明的方法参数类型。看见使用@RequestParam.

请注意,使用@RequestParam是可选的-例如,设置其属性。请参阅本表后面的“任何其他参数”。

@RequestHeader

用于访问请求标头。标头值转换为声明的方法参数类型。看见使用@RequestHeader.

@CookieValue

为了获得饼干。Cookie值转换为声明的方法参数类型。看见使用@CookieValue.

@RequestBody

用于访问HTTP请求主体。将主体内容转换为声明的方法参数类型。HttpMessageReader实例。支持反应性类型。看见使用@RequestBody.

HttpEntity

用于访问请求头和主体。身体被转换成HttpMessageReader实例。支持反应性类型。看见使用HttpEntity.

@RequestPart

中的某个部分。multipart/form-data请求。支持反应性类型。看见多部分内容和多部分数据.

java.util.Maporg.springframework.ui.Model,和org.springframework.ui.ModelMap.

用于访问HTML控制器中使用的模型,并作为视图呈现的一部分公开给模板。

@ModelAttribute

用于访问模型中的现有属性(如果不存在实例化的话),并应用数据绑定和验证。看见使用@ModelAttribute以及使用模型和使用DataBinder.

请注意,使用@ModelAttribute是可选的-例如,设置其属性。请参阅本表后面的“任何其他参数”。

ErrorsBindingResult

用于访问命令对象的验证和数据绑定中的错误(即@ModelAttribute参数)或验证@RequestBody@RequestPart争论。阿Errors,或BindingResult参数必须在验证的方法参数之后立即声明。

SessionStatus+级@SessionAttributes

用于标记表单处理完成,这将触发对通过类级别声明的会话属性的清理。@SessionAttributes注释看见使用@SessionAttributes更多细节。

UriComponentsBuilder

用于准备相对于当前请求的主机、端口、方案和路径的URL。看见URI链接.

@SessionAttribute

对于任何会话属性的访问-与存储在会话中的模型属性相反,这是类级别的结果。@SessionAttributes申报。看见使用@SessionAttribute更多细节。

@RequestAttribute

用于访问请求属性。看见使用@RequestAttribute更多细节。

任何其他论点

如果方法参数与上述任何参数不匹配,则默认情况下,它被解析为@RequestParam如果它是一个简单的类型,由BeanUtils#isSimpleProperty,或作为@ModelAttribute否则。

返回值

与SpringMVC相同

下表显示了支持的控制器方法返回值。注意,来自诸如反应堆、RxJava等库的反应性类型,或其他通常支持所有返回值。

控制器方法返回值 描述

@ResponseBody

返回值通过HttpMessageWriter实例并写入响应。看见使用@ResponseBody.

HttpEntityResponseEntity

返回值指定完整响应,包括HTTP头,主体通过HttpMessageWriter实例并写入响应。看见使用ResponseEntity.

HttpHeaders

用于返回带有标头而不带body的响应。

String

要用ViewResolver实例,并与隐式模型一起使用-通过命令对象和@ModelAttribute方法。处理程序方法还可以通过声明Model论点(描述)更早).

View

View实例与隐式模型一起使用-通过命令对象和@ModelAttribute方法。处理程序方法还可以通过声明Model论点(描述)更早).

java.util.Maporg.springframework.ui.Model

要添加到隐式模型中的属性,视图名称是根据请求路径隐式确定的。

@ModelAttribute

要添加到模型中的属性,视图名称根据请求路径隐式确定。

请注意@ModelAttribute是可选的。请参阅本表后面的“任何其他返回值”。

Rendering

用于模型和视图呈现场景的API。

void

一种具有void,可能是异步的(例如,Mono),返回类型(或null返回值)被认为已经完全处理了响应,如果它还具有ServerHttpResponse..ServerWebExchange争论,或@ResponseStatus注释如果控制器做了一个正的Etag或lastModified时间戳检查。/Todo:见控制器关于细节。

如果上述任何一个都不是真的,则void返回类型还可以指示REST控制器的“无响应体”或HTML控制器的默认视图名称选择。

FluxObservable,或其他反应类型

发出服务器发送的事件。这,这个,那,那个ServerSentEvent当只需要编写数据时,可以省略包装器(但是,text/event-stream必须在映射中通过produces属性)。

任何其他返回值

如果返回值与上述任何内容不匹配,则默认情况下,它被视为视图名称(如果是的话)。Stringvoid(默认视图名称选择适用),或作为要添加到模型中的模型属性,除非它是一个简单类型,如BeanUtils#isSimpleProperty在这种情况下,它仍未解决。

类型转换

与SpringMVC相同

一些表示基于字符串的请求输入的带注释的控制器方法参数(例如,@RequestParam@RequestHeader@PathVariable@MatrixVariable,和@CookieValue)如果参数声明为String.

在这种情况下,类型转换将根据配置的转换器自动应用。默认情况下,简单类型(例如intlongDate)得到支持。类型转换可以通过WebDataBinder(见[mvc-ann-initbinder])或通过登记Formatters带着FormattingConversionService(见弹簧字段格式).

矩阵变量

与SpringMVC相同

RFC 3986讨论路径段中的名称-值对。在SpringWebFlux中,我们将这些称为“矩阵变量”,其基础是“旧员额”由TimBerners-Lee编写,但它们也可以被称为URI路径参数。

矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔-例如,"/cars;color=red,green;year=2012"。还可以通过重复变量名指定多个值-例如,"color=red;color=green;color=blue".

与SpringMVC不同,在WebFlux中,URL中是否存在矩阵变量并不影响请求映射。换句话说,不需要使用URI变量来掩盖变量内容。也就是说,如果要从控制器方法访问矩阵变量,则需要向路径段中添加一个URI变量,其中需要有矩阵变量。下面的示例演示如何做到这一点:

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}

考虑到所有路径段都可以包含矩阵变量,有时可能需要消除矩阵变量应该在哪个路径变量中的歧义,如下例所示:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22
}

可以将矩阵变量定义为可选变量,并指定默认值,如下例所示:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}

若要获取所有矩阵变量,请使用MultiValueMap,如以下示例所示:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap matrixVars,
        @MatrixVariable(pathVar="petId"") MultiValueMap petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}

使用@RequestParam

与SpringMVC相同

您可以使用@RequestParam注释将查询参数绑定到控制器中的方法参数。下面的代码片段显示了使用情况:

@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { 
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}
  使用@RequestParam.
  ServletAPI的“请求参数”概念将查询参数、表单数据和多个部分混为一谈。然而,在WebFlux中,每个都是通过ServerWebExchange。当@RequestParam绑定到查询参数,则可以使用数据绑定将查询参数、表单数据和多个部件应用到命令对象.

方法参数使用@RequestParam默认情况下需要注释,但可以通过设置@RequestParamfalse或者用java.util.Optional包装纸。

如果目标方法参数类型不是String。看见[mvc-ann-type econversion].

@RequestParam注释声明在MapMultiValueMap参数时,映射将使用所有查询参数填充。

请注意,使用@RequestParam是可选的-例如,设置其属性。默认情况下,任何参数都是简单的值类型(由BeanUtils#isSimpleProperty)而不被任何其他参数解析器解析,则将其视为用@RequestParam.

使用@RequestHeader

与SpringMVC相同

您可以使用@RequestHeader注释将请求头绑定到控制器中的方法参数。

下面的示例显示具有标头的请求:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

下面的示例获取Accept-EncodingKeep-Alive标题:

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, 
        @RequestHeader("Keep-Alive") long keepAlive) { 
    //...
}
  的值。Accept-Encoging头球。
  的值。Keep-Alive头球。

如果目标方法参数类型不是String。看见[mvc-ann-type econversion].

@RequestHeader注释用于MapMultiValueMap,或HttpHeaders参数时,映射中填充了所有标头值。

  内置支持可用于将逗号分隔的字符串转换为数组或字符串集合或类型转换系统已知的其他类型。例如,用@RequestHeader("Accept")可能是类型String但也是String[]List.

使用@CookieValue

与SpringMVC相同

您可以使用@CookieValue注释将HTTPcookie的值绑定到控制器中的方法参数。

下面的示例显示带有cookie的请求:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的代码示例演示如何获取cookie值:

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { 
    //...
}
  得到饼干的价值。

如果目标方法参数类型不是String。看见[mvc-ann-type econversion].

使用@ModelAttribute

与SpringMVC相同

您可以使用@ModelAttribute对方法参数进行注释,以访问模型中的属性,如果不存在,则将其实例化。模型属性还与名称与字段名匹配的查询参数和表单字段的值覆盖。这被称为数据绑定,它使您不必处理解析和转换单个查询参数和表单字段的问题。下面的示例绑定Pet:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { } 
  绑定Pet.

这,这个,那,那个Pet前面的示例中的实例按以下方式解析:

  • 如果已经通过使用模型.

  • 从HTTP会话到使用@SessionAttributes.

  • 从默认构造函数的调用。

  • 调用具有匹配查询参数或表单字段的参数的“主构造函数”。参数名是通过JavaBeans确定的。@ConstructorProperties或通过字节码中保留的运行时参数名称。

得到模型属性实例后,应用数据绑定。这,这个,那,那个WebExchangeDataBinder类将查询参数和表单字段的名称与目标上的字段名相匹配。Object。在必要时应用类型转换之后,将填充匹配字段。有关数据绑定(和验证)的更多信息,请参见验证。有关自定义数据绑定的更多信息,请参见使用DataBinder.

数据绑定可能导致错误。默认情况下,WebExchangeBindException但是,若要检查控制器方法中的此类错误,可以添加BindingResult属性旁边的参数。@ModelAttribute,如以下示例所示:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { 
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
  添加BindingResult.

可以在数据绑定之后自动应用验证,方法是添加javax.validation.Valid注释或Spring的@Validated注释(另请参阅bean验证和弹簧验证)下面的示例使用@Valid注释:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { 
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

<1>

SpringWebFlux与SpringMVC不同,它支持模型中的反应性类型-例如,Monoio.reactivex.Single。您可以声明@ModelAttribute参数是否带有反应性类型包装器,如果有必要,将相应地将其解析为实际值。但是,请注意,要使用BindingResult参数时,必须声明@ModelAttribute参数,如前面所示,它之前没有反应式类型包装器。或者,您可以通过反应性类型处理任何错误,如下例所示:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono processSubmit(@Valid @ModelAttribute("pet") Mono petMono) {
    return petMono
        .flatMap(pet -> {
            // ...
        })
        .onErrorResume(ex -> {
            // ...
        });
}

请注意,使用@ModelAttribute是可选的-例如,设置其属性。默认情况下,任何非简单值类型的参数(如BeanUtils#isSimpleProperty)而不被任何其他参数解析器解析,则将其视为用@ModelAttribute.

使用@SessionAttributes

与SpringMVC相同

@SessionAttributes属性中存储模型属性。WebSession在请求之间。它是一个类型级注释,声明特定控制器使用的会话属性。这通常列出模型属性的名称或模型属性的类型,这些属性应该透明地存储在会话中,以供后续的访问请求使用。

考虑以下示例:

@Controller
@SessionAttributes("pet") 
public class EditPetForm {
    // ...
}
  使用@SessionAttributes注释

在第一个请求中,当具有名称的模型属性时,pet,将其添加到模型中,将其自动提升到并保存在WebSession。直到另一个控制器方法使用SessionStatus方法参数来清除存储,如下面的示例所示:

@Controller
@SessionAttributes("pet") 
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) { 
        if (errors.hasErrors) {
            // ...
        }
            status.setComplete();
            // ...
        }
    }
}
  使用@SessionAttributes注释
  使用SessionStatus变量。

使用@SessionAttribute

与SpringMVC相同

如果您需要访问全局管理的预先存在的会话属性(即控制器外部-例如,由筛选器管理),并且可能存在也可能不存在,则可以使用@SessionAttribute方法参数的注释,如下面的示例所示:

@GetMapping("/")
public String handle(@SessionAttribute User user) { 
    // ...
}
  使用@SessionAttribute.

对于需要添加或删除会话属性的用例,请考虑注入WebSession进入控制器方法。

要将会话中的模型属性临时存储为控制器工作流的一部分,请考虑使用SessionAttributes,如上文所述使用@SessionAttributes.

使用@RequestAttribute

与SpringMVC相同

类似于@SessionAttribute,您可以使用@RequestAttribute注释以访问先前创建的请求属性(例如,由WebFilter),如以下示例所示:

@GetMapping("/")
public String handle(@RequestAttribute Client client) { 
    // ...
}
  使用@RequestAttribute.

多部分内容

与SpringMVC相同

如在多部分数据, ServerWebExchange提供对多部分内容的访问。在控制器中处理文件上载表单(例如,从浏览器)的最佳方法是通过数据绑定到命令对象,如以下示例所示:

class MyForm {

    private String name;

    private MultipartFile file;

    // ...

}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        // ...
    }

}

您还可以在RESTful服务场景中提交来自非浏览器客户端的多部分请求。下面的示例与JSON一起使用一个文件:

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
    "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

您可以使用@RequestPart,如以下示例所示:

@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, 
        @RequestPart("file-data") FilePart file) { 
    // ...
}
  使用@RequestPart来获取元数据。
  使用@RequestPart才能拿到文件。

将原始部件内容反序列化(例如,到JSON-类似于@RequestBody),您可以声明一个具体的目标Object,而不是Part,如以下示例所示:

@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { 
    // ...
}
  使用@RequestPart来获取元数据。

你可以用@RequestPart结合javax.validation.Valid或者春天的@Validated注释,这将导致应用StandardBean验证。默认情况下,验证错误会导致WebExchangeBindException,它被转化为400(BAD_REQUEST)回应。或者,您可以通过ErrorsBindingResult参数,如以下示例所示:

@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata, 
        BindingResult result) { 
    // ...
}
  使用@Valid注释
  使用BindingResult争论。

将所有多部分数据作为MultiValueMap,你可以用@RequestBody,如以下示例所示:

@PostMapping("/")
public String handle(@RequestBody Mono> parts) { 
    // ...
}
  使用@RequestBody.

若要顺序访问多部分数据,可以流方式使用@RequestBody带着Flux相反,正如下面的示例所示:

@PostMapping("/")
public String handle(@RequestBody Flux parts) { 
    // ...
}
  使用@RequestBody.

使用@RequestBody

与SpringMVC相同

您可以使用@RequestBody注释将请求体读取并反序列化为Object通过HttpMessageReader。下面的示例使用@RequestBody论点:

@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
    // ...
}

与SpringMVC不同,在WebFlux中,@RequestBody方法参数支持反应性类型和完全无阻塞的读取和(客户端到服务器)流。下面的示例使用Mono:

@PostMapping("/accounts")
public void handle(@RequestBody Mono account) {
    // ...
}

您可以使用http消息编解码器的选项WebFlux Config若要配置或自定义邮件读取器,请执行以下操作。

你可以用@RequestBody结合在一起javax.validation.Valid或者春天的@Validated注释,这将导致应用StandardBean验证。默认情况下,验证错误会导致WebExchangeBindException,它被转化为400(BAD_REQUEST)回应。或者,您可以通过Errors或者是BindingResult争论。下面的示例使用BindingResult辩论`:

@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
    // ...
}

使用HttpEntity

与SpringMVC相同

HttpEntity与使用使用@RequestBody但是基于一个容器对象,该容器对象公开请求头和主体。下面的示例使用HttpEntity:

@PostMapping("/accounts")
public void handle(HttpEntity entity) {
    // ...
}

使用@ResponseBody

与SpringMVC相同

您可以使用@ResponseBody方法的注释,以便通过HttpMessageWriter..下面的示例演示如何做到这一点:

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

@ResponseBody在类级别上也支持它,在这种情况下,它由所有控制器方法继承。这就是@RestController,它只不过是一个标记为@Controller@ResponseBody.

@ResponseBody支持反应性类型,这意味着您可以返回反应堆或RxJava类型,并将它们生成的异步值呈现给响应。有关更多详细信息,请参见http流和JSON渲染.

你可以结合@ResponseBody方法具有JSON序列化视图。看见杰克森·杰森关于细节。

您可以使用http消息编解码器的选项WebFlux Config若要配置或自定义消息写入,请执行以下操作。

使用ResponseEntity

与SpringMVC相同

使用ResponseEntity与使用使用@ResponseBody但是基于指定请求头和正文的容器对象。下面的示例使用ResponseEntity:

@PostMapping("/something")
public ResponseEntity handle() {
    // ...
    URI location = ...
    return new ResponseEntity.created(location).build();
}

杰克森·杰森

Spring提供对Jackson JSON库的支持。

Jackson序列化视图

与SpringMVC相同

SpringWebFlux提供了对杰克逊的系列化观,它只允许呈现Object。用于@ResponseBodyResponseEntity控制器方法,您可以使用Jackson的@JsonView如下面的示例所示,注释可以激活序列化视图类:

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}
  @JsonView允许视图类数组,但只能指定每个控制器方法一个。如果需要激活多个视图,请使用复合接口。

1.11.4。使用模型

与SpringMVC相同

您可以使用@ModelAttribute注释:

  • 在.上方法参数在……里面@RequestMapping方法创建或访问模型中的对象,并通过WebDataBinder.

  • 中的方法级注释。@Controller@ControllerAdvice类之前,帮助初始化模型。@RequestMapping方法调用。

  • 在.上@RequestMapping方法将其返回值标记为模型属性。

本节讨论@ModelAttribute方法,或前面列表中的第二项。控制器可以有任意数量的@ModelAttribute方法。所有这些方法都是在此之前调用的。@RequestMapping方法在同一个控制器中。一个@ModelAttribute方法还可以通过以下方法在控制器之间共享@ControllerAdvice。见控制器通知更多细节。

@ModelAttribute方法具有灵活的方法签名。他们支持许多与@RequestMapping方法(除@ModelAttribute以及任何与请求体相关的内容)。

下面的示例使用@ModelAttribute方法:

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountRepository.findAccount(number));
    // add more ...
}

以下示例仅添加一个属性:

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}
  当未显式指定名称时,将根据Object类型,如Javadoc中所解释的Conventions。通过使用重载,始终可以指定显式名称。addAttribute方法或通过@ModelAttribute(返回值)。

SpringWebFlux与SpringMVC不同,它显式地支持模型中的反应性类型(例如,Monoio.reactivex.Single)对象时,这些异步模型属性可以透明地解析(并更新模型)到它们的实际值。@RequestMapping调用,提供@ModelAttribute参数声明时不带包装器,如下面的示例所示:

@ModelAttribute
public void addAccount(@RequestParam String number) {
    Mono accountMono = accountRepository.findAccount(number);
    model.addAttribute("account", accountMono);
}

@PostMapping("/accounts")
public String handle(@ModelAttribute Account account, BindingResult errors) {
    // ...
}

此外,在视图呈现之前,任何具有反应性类型包装器的模型属性都被解析为它们的实际值(并更新了模型)。

您也可以使用@ModelAttribute的方法级注释@RequestMapping方法的返回值。@RequestMapping方法被解释为模型属性。这通常不是必需的,因为它是HTML控制器中的默认行为,除非返回值是String否则将被解释为视图名。@ModelAttribute还可以帮助自定义模型属性名称,如下面的示例所示:

@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
    // ...
    return account;
}

1.11.5.使用DataBinder

与SpringMVC相同

@Controller@ControllerAdvice类可以@InitBinder方法,以初始化WebDataBinder。这些机构又习惯于:

  • 将请求参数(即表单数据或查询)绑定到模型对象。

  • 转换String-基于目标类型的控制器方法参数的请求值(如请求参数、路径变量、标头、cookie等)。

  • 将模型对象值格式化为String呈现HTML窗体时的值。

@InitBinder方法可以注册特定于控制器的java.bean.PropertyEditor或春天ConverterFormatter组件。此外,还可以使用WebFlux Java配置注册ConverterFormatter全局共享类型FormattingConversionService.

@InitBinder方法支持许多相同的参数@RequestMapping方法做,除了@ModelAttribute(命令对象)参数。通常,它们是用WebDataBinder参数,用于注册,以及void返回值下面的示例使用@InitBinder注释:

@Controller
public class FormController {

    @InitBinder 
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}
  使用@InitBinder注释

或者,当使用Formatter-通过共享进行基于设置的设置FormattingConversionService,您可以重复使用相同的方法并注册特定于控制器的。Formatter实例,如下例所示:

@Controller
public class FormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); 
    }

    // ...
}
  添加自定义格式化程序(DateFormatter,在这种情况下)。

1.11.6。管理异常

与SpringMVC相同

@Controller和@ControllerAdview类可以@ExceptionHandler方法处理控制器方法的异常。以下示例包括这样一个处理程序方法:

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler 
    public ResponseEntity handle(IOException ex) {
        // ...
    }
}
  宣告@ExceptionHandler:

异常可以与正在传播的顶级异常(即直接异常)匹配。IOException)或针对顶层包装器异常中的直接原因(例如,IOException包在IllegalStateException).

为了匹配异常类型,最好将目标异常声明为方法参数,如前面的示例所示。或者,注释声明可以缩小异常类型来匹配。我们通常建议在参数签名中尽可能具体,并在@ControllerAdvice按照相应的顺序排列。看见MVC部分关于细节。

  @ExceptionHandler方法中的方法参数和返回值与@RequestMapping方法,但请求主体除外-和@ModelAttribute-相关的方法论证。

支持@ExceptionHandler在SpringWebFlux中提供的方法由HandlerAdapter@RequestMapping方法。看见使用DispatcherHandler更多细节。

RESTAPI异常

与SpringMVC相同

REST服务的一个常见要求是在响应正文中包含错误详细信息。Spring框架不会自动这样做,因为响应体中错误细节的表示是特定于应用程序的。然而,@RestController能用@ExceptionHandler方法ResponseEntity返回值,以设置响应的状态和正文。这些方法也可以在@ControllerAdvice类来全局应用它们。

  请注意,SpringWebFlux没有与SpringMVC等效的ResponseEntityExceptionHandler,因为WebFlux只引发ResponseStatusException(或其子类),而这些类不需要转换为HTTP状态代码。

1.11.7控制器通知

与SpringMVC相同

通常,@ExceptionHandler@InitBinder,和@ModelAttribute方法在@Controller在其中声明它们的类(或类层次结构)。如果希望这些方法在全局(跨控制器)应用更多,可以在标记为@ControllerAdvice@RestControllerAdvice.

@ControllerAdvice标记为@Component,这意味着此类可以通过以下方式注册为Springbean组件扫描. @RestControllerAdvice也是一个元注释,两者都标记为@ControllerAdvice@ResponseBody,这在本质上意味着@ExceptionHandler方法通过消息转换(相对于视图分辨率或模板呈现)呈现到响应主体。

在启动时,用于@RequestMapping@ExceptionHandler方法检测类型的Springbean@ControllerAdvice并在运行时应用它们的方法。全球@ExceptionHandler方法(来自@ControllerAdvice)适用当地的(从@Controller)相比之下,全球@ModelAttribute@InitBinder应用方法以前本地的。

默认情况下@ControllerAdvice方法应用于每个请求(即所有控制器),但您可以通过注释上的属性将其缩小到控制器的子集,如下例所示:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

前面的选择器在运行时进行评估,如果您广泛使用它们,可能会对性能产生负面影响。见@ControllerAdvice关于更多细节,Javadoc。

1.12.功能端点

SpringWebFlux包括一个轻量级的函数编程模型,在该模型中,函数用于路由和处理请求,合同是为不可变而设计的。它是基于注释的编程模型的另一种选择,但在相同的情况下运行。反应铁心基金会。

1.12.1.概述

处理HTTP请求的方法是HandlerFunction这需要ServerRequest和回报Mono,它们都是不可变的契约,提供了对HTTP请求和响应的JDK 8友好访问。HandlerFunction等于@RequestMapping方法在基于注释的编程模型中。

请求被路由到HandlerFunction带着RouterFunction这需要ServerRequest和回报Mono。当请求与特定路由匹配时,HandlerFunction映射到路由使用。RouterFunction等于@RequestMapping注释

RouterFunctions.route(RequestPredicate, HandlerFunction)提供可与许多内置请求谓词一起使用的路由器功能默认实现,如以下示例所示:

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

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

RouterFunction route =
    route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
        .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
        .andRoute(POST("/person"), handler::createPerson);


public class PersonHandler {

    // ...

    public Mono listPeople(ServerRequest request) {
        // ...
    }

    public Mono createPerson(ServerRequest request) {
        // ...
    }

    public Mono getPerson(ServerRequest request) {
        // ...
    }
}

运行一个RouterFunction就是把它变成HttpHandler并通过一个内置的服务器适配器:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

大多数应用程序可以通过WebFluxJava配置运行,请参阅运行服务器.

1.12.2。手功能

ServerRequestServerResponse是提供JDK 8友好访问HTTP请求和响应的不可变接口。反应流反压力要求和响应体流。请求体用反应堆表示。FluxMono。响应体用任何反应流表示。Publisher,包括FluxMono。有关这方面的更多信息,请参见反应库.

使用ServerRequest

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

下面的示例将请求正文提取到Mono:

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

下面的示例将主体提取为Flux,在哪里Person对象是从某种序列化形式(如JSON或XML)解码的:

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

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

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

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

Mono map = request.body(BodyExtractors.toFormData());

下面的示例演示如何将多部分数据作为映射访问:

Mono map = request.body(BodyExtractors.toMultipartData());

下面的示例演示如何以流式方式访问多部件(每次一个部分):

Flux parts = request.body(BodyExtractos.toParts());

使用ServerResponse

ServerResponse提供对HTTP响应的访问,并且由于它是不可变的,所以可以使用build方法来创建它。可以使用构建器设置响应状态、添加响应头或提供主体。下面的示例使用JSON内容创建一个200(OK)响应:

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

下面的示例演示如何使用Location标题和没有正文:

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

处理程序类

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

HandlerFunction helloWorld =
  request -> ServerResponse.ok().body(fromObject("Hello World"));

这很方便,但是在应用程序中,我们需要多个函数,将相关的处理程序函数组合到一个处理程序中是很有用的(比如@Controller)例如,下面的类公开一个反应性Person储存库:

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.ServerResponse.ok;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class PersonHandler {

    private final PersonRepository repository;

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

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

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

    public Mono getPerson(ServerRequest request) { 
        int personId = Integer.valueOf(request.pathVariable("id"));
        return repository.getPerson(personId)
            .flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromObject(person)))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}
  listPeople是一个返回所有Person在存储库中找到的对象为JSON。
  createPerson是存储新的Person包含在请求体中。请注意PersonRepository.savePerson(Person)回报Mono*空荡荡的Mono当从请求中读取并存储的人时,它会发出完成信号。所以我们使用build(Publisher)方法在接收到完成信号时发送响应(即,当Person已获救)。
  getPerson对象标识的返回单个人的处理程序函数。id路径变量我们把它拿回来Person并创建一个JSON响应,如果找到的话。如果找不到,我们就用switchIfEmpty(Mono)若要返回404未找到的响应,请执行以下操作。

1.12.3.使用RouterFunction

RouterFunction用于将请求路由到HandlerFunction。通常,您不自己编写路由器函数,而是使用RouterFunctions.route(RequestPredicate, HandlerFunction)。如果应用谓词,则将请求路由到给定的HandlerFunction。否则,不执行路由,这将转换为404(未找到)响应。

使用谓词

你可以自己写RequestPredicate,但是RequestPredicates实用工具类提供了基于请求路径、HTTP方法、内容类型等的常用实现。下面的示例基于路径创建请求谓词:

RouterFunction route =
    RouterFunctions.route(RequestPredicates.path("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")));

可以使用以下方法将多个请求谓词组合在一起:

  • RequestPredicate.and(RequestPredicate)-两者必须匹配。

  • RequestPredicate.or(RequestPredicate)-两者都能匹配。

的许多谓词RequestPredicates都是由人组成的。例如RequestPredicates.GET(String)是由RequestPredicates.method(HttpMethod)RequestPredicates.path(String).

您可以将多个路由器功能组合成一个,以便按顺序对它们进行评估,如果第一个路由不匹配,则计算第二个路由。你可以宣布更多的具体路线,而不是更一般的路线。

路线

您可以使用以下方法将多个路由器功能组合在一起:

  • RouterFunction.and(RouterFunction)

  • RouterFunction.andRoute(RequestPredicate, HandlerFunction)-捷径RouterFunction.and()嵌套式RouterFunctions.route().

然后,我们可以使用组合路由和谓词声明以下路由,引用PersonHandler(见[webflow-fn-处理程序-类])通过方法-参考:

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

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

RouterFunction personRoute =
    route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
        .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
        .andRoute(POST("/person"), handler::createPerson);

1.12.4运行服务器

如何在HTTP服务器中运行路由器功能?一个简单的选项是将路由器功能转换为HttpHandler使用下列方法之一:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

然后,可以使用返回的HttpHandler使用多个服务器适配器,方法如下HttpHandler用于特定于服务器的说明。

一个更高级的选项是使用DispatcherHandler-通过WebFlux Config,它使用Spring配置声明处理请求所需的组件。WebFlux Java配置声明了以下支持功能端点的基础设施组件:

  • RouterFunctionMapping*检测一个或多个RouterFunctionSpring配置中的bean,通过RouterFunction.andOther,并将请求路由到生成的组合RouterFunction.

  • HandlerFunctionAdapter*简单适配器,它允许DispatcherHandler调用HandlerFunction映射到请求。

  • ServerResponseResultHandler:处理调用HandlerFunction通过调用writeTo方法ServerResponse.

前面的组件使功能端点适合于DispatcherHandler请求处理生命周期,并且(可能)与注释的控制器并行运行(如果有声明的话)。这也是SpringBootWebFlux启动程序如何启用功能端点。

下面的示例显示了WebFluxJava配置(请参阅发散器(关于如何运行它):

@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.12.5.使用HandlerFilterFunction

您可以通过调用RouterFunction.filter(HandlerFilterFunction),在哪里HandlerFilterFunction本质上是一个函数,它接受ServerRequestHandlerFunction并返回ServerResponse。处理程序函数参数表示链中的下一个元素。这是典型的HandlerFunction它被路由到,但也可以是另一个FilterFunction如果应用了多个过滤器。使用注释,您可以通过以下方法实现类似的功能@ControllerAdvice..ServletFilter或者两者兼而有之。现在我们可以在我们的路由中添加一个简单的安全过滤器,假设我们有一个SecurityManager它可以确定是否允许特定路径。下面的示例演示如何做到这一点:

import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction route = ...

RouterFunction filteredRoute =
    route.filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
  });

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

  CORS对功能端点的支持是通过一个专用的CorsWebFilter.

1.13.URI链接

与SpringMVC相同

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

1.13.1.UriComponents

SpringMVC和SpringWebFlux

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

UriComponents uriComponents = UriComponentsBuilder
        .fromUriString("http://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("http://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("Westin", "123")
        .toUri();

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

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

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

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

1.13.2。UriBuilder

SpringMVC和SpringWebFlux

UriComponentsBuilder实施器UriBuilder。您可以创建一个UriBuilder,反过来,UriBuilderFactory。一起,UriBuilderFactoryUriBuilder根据共享配置(如基本URL、编码首选项和其他详细信息),提供一种可插入的机制来从URI模板构建URI。

您可以配置RestTemplateWebClient带着UriBuilderFactory自定义URI的准备。DefaultUriBuilderFactory的默认实现。UriBuilderFactoryUriComponentsBuilder内部并公开共享配置选项。

下面的示例演示如何配置RestTemplate:

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

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

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

下面的示例配置WebClient:

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

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

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

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

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

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

1.13.3.URI编码

SpringMVC和SpringWebFlux

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

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

  • UriComponents#encode()*编码URI组件URI变量被展开。

这两个选项都用转义八进制替换非ASCII和非法字符。但是,第一个选项还替换了出现在URI变量中的保留字符。

  考虑“;”,这在一条道路上是合法的,但有保留的意义。第一个选项将URI变量中的“%3B”替换为“;”,而不是在URI模板中。相反,第二个选项永远不会取代“;”,因为它是路径中的一个法律性质。

在大多数情况下,第一个选项可能会给出预期的结果,因为它将URI变量视为要完全编码的不透明数据,而选项2只有在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.fromPath("/hotel list/{city}?q={q}")
            .build("New York", "foo+bar")

这,这个,那,那个WebClientRestTemplate方法在内部展开和编码URI模板。UriBuilderFactory战略。两者都可以使用自定义策略配置。如下例所示:

String baseUrl = "http://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();

这,这个,那,那个DefaultUriBuilderFactory实施用途UriComponentsBuilder内部展开和编码URI模板。作为一个工厂,它提供了基于以下编码模式之一配置编码方法的单一位置:

  • TEMPLATE_AND_VALUES*用途UriComponentsBuilder#encode(),对应于前面列表中的第一个选项,对URI模板进行预编码,并在展开时严格编码URI变量。

  • VALUES_ONLY*不对URI模板进行编码,而是通过以下方式对URI变量应用严格编码UriUtils#encodeUriUriVariables在将它们扩展到模板之前。

  • URI_COMPONENTS*用途UriComponents#encode(),对应于前面列表中的第二个选项,对URI组件值进行编码。URI变量被展开。

  • NONE*不使用编码。

这,这个,那,那个RestTemplate设置为EncodingMode.URI_COMPONENTS出于历史原因和向后兼容性。这,这个,那,那个WebClient中的默认值。DefaultUriBuilderFactory,这是从EncodingMode.URI_COMPONENTS在5.0.x至EncodingMode.TEMPLATE_AND_VALUES in 5.1.

1.14.CORS

与SpringMVC相同

SpringWebFlux允许您处理CORS(跨源资源共享)。本节介绍如何做到这一点。

1.14.1.导言

与SpringMVC相同

出于安全考虑,浏览器禁止对当前来源以外的资源进行Ajax调用。例如,您可以在一个选项卡上有您的银行帐户,而在另一个选项卡中有evil.com。evil.com的脚本不应该使用您的凭据向您的银行API发出Ajax请求-例如,从您的帐户中取款!

跨源资源共享(CORS)是W3C规范由大多数浏览器这允许您指定哪些类型的跨域请求是授权的,而不是使用基于iframe或jsonp的安全性较低、功能较弱的解决方案。

1.14.2。加工

与SpringMVC相同

CORS规范区分了飞行前请求、简单请求和实际请求。要了解CORS的工作原理,您可以阅读这篇文章,或参阅规范以获得更多细节。

春季WebFluxHandlerMapping实现为CORS提供内置支持。成功地将请求映射到处理程序之后,HandlerMapping检查给定请求和处理程序的CORS配置,并采取进一步的操作。直接处理飞行前请求,同时拦截、验证简单和实际的CORS请求,并设置所需的CORS响应头。

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

HandlerMapping可以配置单独使用基于URL模式的CorsConfiguration映射。在大多数情况下,应用程序使用WebFluxJava配置来声明这样的映射,这将导致向所有人传递一个单一的全局映射。HadlerMappping实现。

可以将全局CORS配置组合在HandlerMapping级别具有更细粒度、处理程序级别的CORS配置。例如,带注释的控制器可以使用类或方法级别。@CrossOrigin注释(其他处理程序可以实现CorsConfigurationSource).

结合全局和本地配置的规则通常是附加的-例如,所有全局和所有本地源。对于只能接受单个值的属性,如allowCredentialsmaxAge,本地重写全局值。看见CorsConfiguration#combine(CorsConfiguration)更多细节。

 

若要从源中了解更多信息或进行高级自定义,请参见:

  • CorsConfiguration

  • CorsProcessorDefaultCorsProcessor

  • AbstractHandlerMapping

1.14.3.使用@CrossOrigin

与SpringMVC相同

这,这个,那,那个@CrossOrigin注释在带注释的控制器方法上启用跨源请求,如下例所示:

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

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

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

默认情况下,@CrossOrigin允许:

  • 所有的起源。

  • 所有标题。

  • 将控制器方法映射到的所有HTTP方法。

allowedCredentials默认情况下不启用,因为这建立了一个信任级别,该级别公开敏感的用户特定信息(例如cookie和CSRF令牌),并且只应在适当的情况下使用。

maxAge设置为30分钟。

@CrossOrigin在类级别也支持,并由所有方法继承。下面的示例指定某个域并设置maxAge至一小时:

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

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

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

你可以用@CrossOrigin在类和方法级别上,如下面的示例所示:

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

    @CrossOrigin("http://domain2.com") 
    @GetMapping("/{id}")
    public Mono retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono remove(@PathVariable Long id) {
        // ...
    }
}
  使用@CrossOrigin在课堂上。
  使用@CrossOrigin在方法层面。

1.14.4全局配置

与SpringMVC相同

除了细粒度的控制器方法级配置之外,您还可能需要定义一些全局CORS配置。可以设置基于URL的CorsConfiguration任何HandlerMapping..然而,大多数应用程序都使用WebFluxJava配置来实现这一点。

默认情况下,全局配置支持以下功能:

  • 所有的起源。

  • 所有标题。

  • GETHEAD,和POST方法。

allowedCredentials默认情况下不启用,因为这建立了一个信任级别,该级别公开敏感的用户特定信息(例如cookie和CSRF令牌),并且只应在适当的情况下使用。

maxAge设置为30分钟。

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

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

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

        // Add more mappings...
    }
}

1.14.5。CORSWebFilter

与SpringMVC相同

您可以通过内置的方法应用cors支持。CorsWebFilter,这与功能端点.

若要配置筛选器,可以声明CorsWebFilter豆子和传球CorsConfigurationSource它的构造函数,如下面的示例所示:

@Bean
CorsWebFilter corsFilter() {

    CorsConfiguration config = new CorsConfiguration();

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

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

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

    return new CorsWebFilter(source);
}

1.15.Web安全

与SpringMVC相同

这,这个,那,那个弹簧安全项目提供了对保护Web应用程序免受恶意攻击的支持。请参阅Spring安全参考文档,包括:

  • WebFlux安全

  • WebFlux测试支持

  • CSRF保护

  • 安全响应头

1.16。视图技术

与SpringMVC相同

在SpringWebFlux中使用视图技术是可插拔的。无论您决定使用Thymeleaf、FreeMarker还是其他视图技术,都主要是一个配置更改的问题。本章介绍与SpringWebFlux集成的视图技术。我们假设你已经很熟悉视图解析.

1.16.1。齐米列夫

与SpringMVC相同

Thymeleaf是一个现代的服务器端Java模板引擎,它强调可以通过双击在浏览器中预览的自然HTML模板,这对于不需要运行服务器的UI模板(例如,设计人员)的独立工作非常有用。Thymeleaf提供了一套广泛的特性,并且它是积极开发和维护的。有关更完整的介绍,请参见齐米列夫项目主页。

Thymeleaf与SpringWebFlux的集成由Thymeleaf项目管理。该配置涉及一些bean声明,例如SpringResourceTemplateResolverSpringWebFluxTemplateEngine,和ThymeleafReactiveViewResolver。有关详细信息,请参阅Thymeleaf+Spring和WebFlux集成公告.

1.16.2。费标

与SpringMVC相同

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

视图配置

与SpringMVC相同

下面的示例演示如何将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;
    }
}

模板需要存储在FreeMarkerConfigurer,如前面的示例所示。给定前面的配置,如果控制器返回视图名,welcome,解析器将查找classpath:/templates/freemarker/welcome.ftl模板。

FreeMarker配置

与SpringMVC相同

您可以将FreeMarker‘Settings’和‘SharedVariable’直接传递给FreeMarkerConfiguration对象(由Spring管理)通过在FreeMarkerConfigurer豆子这,这个,那,那个freemarkerSettings属性需要java.util.Properties对象,以及freemarkerVariables属性需要java.util.Map。下面的示例演示如何使用FreeMarkerConfigurer:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // ...

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

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

有关设置和变量的详细信息,请参阅FreeMarker文档,因为它们适用于Configuration对象。

1.16.3。脚本视图

与SpringMVC相同

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

脚本库 脚本引擎

车把

纳什恩

胡须

纳什恩

反应

纳什恩

ejs

纳什恩

Erb

JRuby

字符串模板

Jython

Kotlin脚本模板

科特林

  集成任何其他脚本引擎的基本规则是,它必须实现ScriptEngineInvocable接口。

所需

与SpringMVC相同

您需要在类路径上使用脚本引擎,其细节因脚本引擎而异:

  • 这,这个,那,那个纳什恩Java 8+提供了JavaScript引擎。强烈建议使用最新的可用更新版本。

  • JRuby应该添加为Ruby支持的依赖项。

  • Jython应该作为Python支持的依赖项添加。

  • org.jetbrains.kotlin:kotlin-script-util依赖性和aMETA-INF/services/javax.script.ScriptEngineFactory包含org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory为了支持Kotlin脚本,应该添加行。看见这个例子更多细节。

您需要脚本模板库。实现Javascript的一种方法是通过WebJars.

脚本模板

与SpringMVC相同

您可以声明ScriptTemplateConfigurerbean指定要使用的脚本引擎、要加载的脚本文件、调用什么函数来呈现模板,等等。下面的示例使用了穆斯塔赫模板和Nashorn JavaScript引擎:

@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*RenderingContext它提供了对应用程序上下文、区域设置、模板加载程序和URL的访问(自5.0起)。

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

如果模板技术需要一些定制,则可以提供实现自定义呈现函数的脚本。例如汉德尔巴尔斯在使用模板之前需要编译它们,并且需要聚填充为了模拟服务器端脚本引擎中无法使用的一些浏览器工具.下面的示例演示如何设置自定义呈现函数:

@Configuration
@EnableWebMvc
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;
    }
}
  设置sharedEngine财产false当使用非线程安全脚本引擎时,需要使用非用于并发性的模板库,例如把手或在纳什霍恩上运行的响应。在这种情况下,需要Java 8u60或更高版本,因为这个虫子.

polyfill.js只定义window对象,如下面的代码段所示:

var window = {};

这个基本render.js实现在使用模板之前编译它。生产就绪实现还应该存储和重用缓存模板或预编译模板。这可以在脚本端完成,以及您需要的任何定制(例如管理模板引擎配置)。下面的示例演示如何编译模板:

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

查看SpringFramework单元测试,爪哇,和资源,以获取更多配置示例。

1.16.4。JSON和XML

与SpringMVC相同

为内容协商根据客户机请求的内容类型,能够在用HTML模板呈现模型还是以其他格式(例如JSON或XML)呈现模型是非常有用的。为了支持这样做,SpringWebFlux提供了HttpMessageWriterView,可以用来插入任何可用的编解码器从…spring-web,如Jackson2JsonEncoderJackson2SmileEncoder,或Jaxb2XmlEncoder.

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

在大多数情况下,模型包含多个属性。若要确定要序列化哪一个,可以配置HttpMessageWriterView具有要用于呈现的模型属性的名称。如果模型只包含一个属性,则使用该属性。

1.17.http缓存

与SpringMVC相同

HTTP缓存可以显著提高Web应用程序的性能。HTTP缓存围绕Cache-Control响应头和随后的条件请求头,如Last-ModifiedETagCache-Control建议私有(例如浏览器)和公共(例如,代理)缓存如何缓存和重用响应。阿ETag报头用于发出条件请求,如果内容没有更改,则可能导致304(NOT_MODIAD)没有正文。ETag可以看作是对Last-Modified头球。

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

1.17.1. CacheControl

与SpringMVC相同

CacheControl控件相关的设置提供支持。Cache-Control标题,并在许多地方被接受为参数:

  • 控制器

  • 静态资源

当RFC 7234控件的所有可能指令。Cache-Control响应头,CacheControltype采用了一种面向用例的方法,该方法侧重于常见的场景,如下面的示例所示:

// 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.17.2。控制器

与SpringMVC相同

控制器可以添加对HTTP缓存的显式支持。我们建议这样做,因为lastModifiedETag在将资源与条件请求头进行比较之前,需要计算资源的值。控制器可以添加ETagCache-Control设置为ResponseEntity,如以下示例所示:

@GetMapping("/book/{id}")
public ResponseEntity 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_MODATED)响应。否则,ETagCache-Control标题被添加到响应中。

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

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

    long eTag = ... 

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

    model.addAttribute(...); 
    return "myViewName";
}
  具体应用计算。
  响应已设置为304(NOT_MODATED)。没有进一步的处理。
  继续处理请求。

有三个变体用于检查有条件的请求。eTag价值,lastModified价值观,或者两者兼而有之。有条件的GETHEAD请求时,可以将响应设置为304(NOT_MODATED)。有条件的POSTPUT,和DELETE,您可以将响应设置为409(PREATION_FLOCK),以防止并发修改。

1.17.3。静态资源

与SpringMVC相同

应该使用Cache-Control和条件响应报头以获得最佳性能。请参阅有关配置的一节。静态资源.

1.18。WebFlux Config

与SpringMVC相同

WebFluxJava配置声明了使用带注释的控制器或功能端点处理请求所需的组件,并提供了一个自定义配置的API。这意味着您不需要理解Java配置创建的底层bean。然而,如果你想要理解它们,你可以看到它们WebFluxConfigurationSupport或者阅读更多关于它们的内容。特殊豆类.

对于更高级的自定义(在配置API中不可用),可以通过高级配置模式.

1.18.1。启用WebFlux配置

与SpringMVC相同

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

@Configuration
@EnableWebFlux
public class WebConfig {
}

前面的示例注册了许多SpringWebFlux基础设施bean并适应于类路径上可用的依赖项-用于JSON、XML和其他。

1.18.2。WebFlux配置API

与SpringMVC相同

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

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // Implement configuration methods...

}

1.18.3。转换,格式化

与SpringMVC相同

默认情况下,格式化程序用于NumberDate类型,包括对@NumberFormat@DateTimeFormat注释。如果类路径上存在Joda-time,也会安装对Joda-Time格式库的完全支持。

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

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

}
  看见FormatterRegistrarSPI而FormattingConversionServiceFactoryBean有关何时使用FormatterRegistrar实现。

1.18.4。验证

与SpringMVC相同

默认情况下,如果bean验证存在于类路径(例如Hibernate Validator)中,LocalValidatorFactoryBean注册为验证器用于@ValidValidated在……上面@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());
    }

}
  如果你需要一个LocalValidatorFactoryBean注入某个地方,创建一个bean并将其标记为@Primary为了避免与MVC配置中声明的冲突。

1.18.5。内容类型解析器

与SpringMVC相同

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

下面的示例演示如何自定义请求的内容类型解析:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

1.18.6。http消息编解码器

与SpringMVC相同

下面的示例演示如何自定义如何读取和写入请求和响应主体:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

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

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

  • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES是残疾的。

  • MapperFeature.DEFAULT_VIEW_INCLUSION是残疾的。

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

  • jackson-datatype-jdk7支持Java 7类型,如java.nio.file.Path.

  • jackson-datatype-joda支持Joda-time类型。

  • jackson-datatype-jsr310支持Java 8日期和时间API类型。

  • jackson-datatype-jdk8支持其他Java 8类型,如Optional.

1.18.7。视图解析器

与SpringMVC相同

下面的示例演示如何配置视图解析:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

这,这个,那,那个ViewResolverRegistry有用于Spring框架集成的视图技术的快捷方式。下面的示例使用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);
    }
}

支持内容协商并通过视图解析(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));
    }

    // ...
}

看见视图技术有关与SpringWebFlux集成的视图技术的更多信息。

1.18.8。静态资源

与SpringMVC相同

此选项提供了一种方便的方法,可以从以下列表中为静态资源提供服务:Resource-以地点为基地。

在下一个示例中,给定一个以/resources,相对路径用于查找和服务相对于/static在类路径上。为资源提供一年的未来到期时间,以确保最大限度地使用浏览器缓存和减少浏览器发出的HTTP请求。这,这个,那,那个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));
    }

}

资源处理程序还支持ResourceResolver实现和ResourceTransformer实现,可用于创建用于使用优化资源的工具链。

您可以使用VersionResourceResolver对于基于内容、固定应用程序版本或其他信息计算的MD5散列的版本资源URL。阿ContentVersionStrategy(MD5散列)是一个很好的选择,但有一些明显的例外(例如模块加载器使用的JavaScript资源)。

下面的示例演示如何使用VersionResourceResolver在Java配置中:

@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这样它就可以被注射到其他人身上了。

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

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

WebJars也通过WebJarsResourceResolver并在下列情况下自动注册:org.webjars:webjars-locator存在于类路径上。解析器可以重写URL以包含JAR的版本,也可以在没有版本的情况下与传入的URL匹配(例如,/jquery/jquery.min.js/jquery/1.2.0/jquery.min.js).

1.18.9。路径匹配

与SpringMVC相同

您可以自定义与路径匹配相关的选项。有关个别选项的详细信息,请参阅PathMatchConfigurer贾瓦多克。下面的示例演示如何使用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));
    }

}
 

SpringWebFlux依赖于请求路径的解析表示形式,称为RequestPath用于访问解码后的路径段值,删除分号内容(即路径或矩阵变量)。这意味着,与SpringMVC不同的是,您不需要指示是解码请求路径,还是为了路径匹配目的删除分号内容。

SpringWebFlux也不支持后缀模式匹配,与SpringMVC不同,在SpringMVC中我们也支持推荐不再依赖它。

1.18.10。高级配置模式

与SpringMVC相同

@EnableWebFlux进口品DelegatingWebFluxConfiguration这一点:

  • 为WebFlux应用程序提供默认的Spring配置

  • 检测并委托给WebFluxConfigurer实现来自定义该配置。

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

@Configuration
public class WebConfig extends DelegatingWebFluxConfiguration {

    // ...

}

可以将现有方法保存在WebConfig,但是您现在也可以重写基类中的bean声明,并且仍然有许多其他声明。WebMvcConfigurer类路径上的实现。

1.19.http/2

与SpringMVC相同

支持HTTP/2需要Servlet 4容器,SpringFramework 5与ServletAPI 4兼容。从编程模型的角度来看,应用程序不需要做任何具体的事情。但是,有一些与服务器配置相关的注意事项。有关详细信息,请参阅http/2 wiki页面.

目前,SpringWebFlux不支持Netty的HTTP/2。也不支持以编程方式将资源推送到客户端。

2.WebClient

SpringWebFlux包括一个反应性的、非阻塞的WebClient对于HTTP请求。客户端具有功能良好的api,具有声明式组合的反应性类型,请参阅反应库。WebFlux客户端和服务器依赖于相同的非阻塞。编解码器若要对请求和响应内容进行编码和解码,请执行以下操作。

内部WebClient委托给HTTP客户端库。默认情况下,它使用反应堆Netty,对Jetty有内置的支持。反应性HtpClient,而其他的则可以通过ClientHttpConnector.

2.1.配置

创建WebClient是通过一种静态工厂方法:

  • WebClient.create()

  • WebClient.create(String baseUrl)

以上方法使用反应堆Netty。HttpClient使用默认设置和Expectio.projectreactor.netty:reactor-netty在类路径上。

您也可以使用WebClient.builder()还有其他选择:

  • uriBuilderFactory*定制UriBuilderFactory作为基URL使用。

  • defaultHeader*每个请求的标题。

  • defaultCookie*每次请求都提供饼干。

  • defaultRequestConsumer定制每个请求。

  • filter对每个请求进行客户端筛选。

  • exchangeStrategies*http消息读取器/写入器自定义。

  • clientConnector*http客户端库设置。

下面的示例配置http码:

    ExchangeStrategies strategies = ExchangeStrategies.builder()
            .codecs(configurer -> {
                // ...
            })
            .build();

    WebClient client = WebClient.builder()
            .exchangeStrategies(strategies)
            .build();

一旦建成,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.反应堆Netty

若要自定义反应堆Netty设置,请简单地提供预先配置的HttpClient:

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

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

资源

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

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

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

您还可以选择不参与全球反应堆Netty资源。但是,在这种模式下,您需要确保所有反应堆Netty客户端和服务器实例使用共享资源,如下例所示:

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

    @Bean
    public WebClient webClient() {

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

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

        return WebClient.builder().clientConnector(connector).build(); 
    }
  创建独立于全球资源的资源。
  使用ReactorClientHttpConnector具有资源工厂的构造函数。
  将连接器插入WebClient.Builder.

超时

若要配置连接超时,请执行以下操作:

import io.netty.channel.ChannelOption;

HttpClient httpClient = HttpClient.create()
        .tcpConfiguration(client ->
                client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000));

若要配置读和/或写入超时值,请执行以下操作:

import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;

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

2.1.2.码头

下面的示例演示如何自定义JettyHttpClient设置:

    HttpClient httpClient = new HttpClient();
    httpClient.setCookieStore(...);
    ClientHttpConnector connector = new JettyClientHttpConnector(httpClient);

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

默认情况下,HttpClient创建自己的资源(ExecutorByteBufferPoolScheduler),它们一直处于活动状态,直到进程退出或stop()叫做。

您可以在Jetty客户机(和服务器)的多个实例之间共享资源,并确保在Spring时关闭资源ApplicationContext通过声明类型的Spring托管bean来关闭JettyResourceFactory,如以下示例所示:

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

    @Bean
    public WebClient webClient() {

        Consumer customizer = client -> {
            // Further customizations...
        };

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

        return WebClient.builder().clientConnector(connector).build(); 
    }
  创建共享资源。
  使用JettyClientHttpConnector具有资源工厂的构造函数。
  将连接器插入WebClient.Builder.

2.2.使用retrieve()

这,这个,那,那个retrieve()方法是获取响应体并对其进行解码的最简单方法。下面的示例演示如何做到这一点:

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

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

您还可以获得从响应中解码的对象流,如下面的示例所示:

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

默认情况下,带有4xx或5xx状态代码的响应将导致WebClientResponseException或者它的一个特定于HTTP状态的子类,例如WebClientResponseException.BadRequestWebClientResponseException.NotFound和其他人。您还可以使用onStatus方法来自定义结果异常,如下面的示例所示:

    Mono result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .onStatus(HttpStatus::is4xxServerError, response -> ...)
            .onStatus(HttpStatus::is5xxServerError, response -> ...)
            .bodyToMono(Person.class);

2.3.使用exchange()

这,这个,那,那个exchange()方法提供比retrieve方法。下面的示例等效于retrieve()还提供了对ClientResponse:

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

在这个级别上,您还可以创建一个完整的ResponseEntity:

    Mono> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .exchange()
            .flatMap(response -> response.toEntity(Person.class));

注意(不像)retrieve())exchange(),对于4xx和5xx的响应没有自动错误信号。您必须检查状态代码并决定如何进行。

  当你使用exchange(),您必须始终使用bodytoEntity方法ClientResponse以确保资源被释放,并避免HTTP连接池的潜在问题。你可以用bodyToMono(Void.class)如果没有预期的响应内容。但是,如果响应有内容,则连接将关闭,并且不会被放回池中。

2.4.请求体

请求体可以从Object,如以下示例所示:

    Mono personMono = ... ;

    Mono result = client.post()
            .uri("/persons/{id}", id)
            .contentType(MediaType.APPLICATION_JSON)
            .body(personMono, Person.class)
            .retrieve()
            .bodyToMono(Void.class);

还可以对象流进行编码,如以下示例所示:

    Flux personFlux = ... ;

    Mono result = client.post()
            .uri("/persons/{id}", id)
            .contentType(MediaType.APPLICATION_STREAM_JSON)
            .body(personFlux, Person.class)
            .retrieve()
            .bodyToMono(Void.class);

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

    Person person = ... ;

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

2.4.1.表格数据

若要发送表单数据,可以提供MultiValueMap作为尸体。请注意,内容自动设置为application/x-www-form-urlencodedFormHttpMessageWriter。下面的示例演示如何使用MultiValueMap:

    MultiValueMap formData = ... ;

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

还可以使用BodyInserters,如以下示例所示:

    import static org.springframework.web.reactive.function.BodyInserters.*;

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

2.4.2.多部分数据

要发送多部分数据,需要提供MultiValueMap其值为Object表示部分内容或HttpEntity表示部件的内容和标题的实例。MultipartBodyBuilder提供一个方便的API来准备多部分请求。下面的示例演示如何创建MultiValueMap:

    MultipartBodyBuilder builder = new MultipartBodyBuilder();
    builder.part("fieldPart", "fieldValue");
    builder.part("filePart", new FileSystemResource("...logo.png"));
    builder.part("jsonPart", new Person("Jason"));

    MultiValueMap> parts = builder.build();

在大多数情况下,您不必指定Content-Type每个部分。内容类型是根据HttpMessageWriter选择序列化它,或者,如果是Resource,基于文件扩展名。如果有必要,可以显式提供MediaType通过一个重载的构建器对每个部件使用part方法。

一次MultiValueMap是准备好的,最简单的方法是将它传递给WebClient是通过syncBody方法,如下面的示例所示:

   MultipartBodyBuilder builder = ...;

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

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

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

    import static org.springframework.web.reactive.function.BodyInserters.*;

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

2.5.客户端过滤器

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

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 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.测试

若要测试使用WebClient,您可以使用模拟web服务器,例如OKHttp MockWebServer..若要查看其使用示例,请检查WebClientIntegrationTests在Spring框架测试或static-serverOkHttp存储库中的示例。

3.WebSocket

与servlet堆栈相同

参考文档的这一部分涵盖了对反应性堆栈WebSocket消息传递的支持。

3.1.WebSocket简介

WebSocket协议RFC 6455,提供了一种标准化的方法,用于在客户端和服务器之间通过单个tcp连接建立全双工双向通信通道。它是一种不同于HTTP的TCP协议,但被设计成在HTTP上工作,使用端口80和443,并允许重用现有的防火墙规则。

WebSocket交互以使用HTTP的HTTP请求开始Upgrade要升级或在本例中切换到WebSocket协议的标头。下面的示例显示了这种交互:

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket <1>
Connection: Upgrade <2>
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
  这,这个,那,那个Upgrade头球。
  使用Upgrade连接。

与通常的200状态代码不同,具有WebSocket支持的服务器返回的输出类似于以下内容:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp

成功握手后,HTTP升级请求的底层TCP套接字将保持打开状态,以便客户端和服务器继续发送和接收消息。

WebSocket工作方式的完整介绍超出了本文档的范围。请参阅RFC 6455,HTML 5的WebSocket一章,或Web上的许多介绍和教程中的任何一个。

注意,如果WebSocket服务器运行在Web服务器(例如nginx)后面,您可能需要将它配置为将WebSocket升级请求传递到WebSocket服务器。同样,如果应用程序在云环境中运行,请检查云提供商与WebSocket支持相关的说明。

3.1.1.http与WebSocket

尽管WebSocket被设计为与HTTP兼容并以HTTP请求开始,但重要的是要理解这两种协议导致非常不同的体系结构和应用程序编程模型。

在HTTP和REST中,应用程序建模为多个URL。为了与应用程序交互,客户端访问那些URL,请求-响应样式。服务器根据HTTPURL、方法和标头将请求路由到适当的处理程序。

相比之下,在WebSocket中,初始连接通常只有一个URL。随后,所有应用程序消息都在同一TCP连接上流动。这指向了一个完全不同的异步、事件驱动的消息传递体系结构。

WebSocket也是一种低级的传输协议,与HTTP不同,它不对消息的内容规定任何语义。这意味着,除非客户端和服务器在消息语义上达成一致,否则无法路由或处理消息。

WebSocket客户端和服务器可以通过Sec-WebSocket-ProtocolHTTP握手请求的标头。在没有这一点的情况下,他们需要有自己的惯例。

3.1.2.何时使用WebSocket

WebSocket可以使网页具有动态和交互性。但是,在许多情况下,Ajax和HTTP流或长轮询相结合可以提供简单有效的解决方案。

例如,新闻、邮件和社交提要需要动态更新,但每隔几分钟就可以这样做。另一方面,合作、游戏和金融应用需要更接近实时。

仅仅延迟并不是一个决定性因素。如果消息量相对较低(例如,监视网络故障),HTTP流或轮询可以提供有效的解决方案。正是低延迟、高频率和高容量的结合,才是WebSocket使用的最佳案例。

还请记住,在Internet上,超出您控制范围的限制性代理可能会阻止WebSocket交互,因为它们不是配置为传递Upgrade标头,或者是因为它们关闭了看似空闲的长寿命连接。这意味着对防火墙内部应用程序使用WebSocket比对面向公共的应用程序使用WebSocket要简单得多。

3.2.WebSocket API

与servlet堆栈相同

SpringFramework提供了一个WebSocketAPI,您可以使用它来编写处理WebSocket消息的客户端和服务器端应用程序。

3.2.1.服务器

与servlet堆栈相同

若要创建WebSocket服务器,可以首先创建WebSocketHandler..下面的示例演示如何做到这一点:

import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

public class MyWebSocketHandler implements WebSocketHandler {

    @Override
    public Mono handle(WebSocketSession session) {
        // ...
    }
}

然后,您可以将其映射到URL并添加一个WebSocketHandlerAdapter,如以下示例所示:

@Configuration
static class WebConfig {

    @Bean
    public HandlerMapping handlerMapping() {
        Map map = new HashMap<>();
        map.put("/path", new MyWebSocketHandler());

        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setUrlMap(map);
        mapping.setOrder(-1); // before annotated controllers
        return mapping;
    }

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}

3.2.2.使用WebSocketHandler

这,这个,那,那个handle方法WebSocketHandler取走WebSocketSession和回报Mono指示会话的应用程序处理何时完成。会话通过两个流来处理,一个流用于入站,另一个用于出站消息。下表描述了处理流的两个方法:

WebSocketSession方法 描述

Flux receive()

提供对入站消息流的访问,并在连接关闭时完成。

Mono send(Publisher)

获取传出消息的源,写入消息,并返回Mono这在源代码完成并完成编写时完成。

WebSocketHandler必须将入站流和出站流组合成一个统一的流,然后返回Mono这反映了这一流动的完成。根据应用程序需求,统一流程在以下情况下完成:

  • 入站或出站消息流完成。

  • 入站流完成(即连接关闭),而出站流是无限的。

  • 在选定的点,通过close方法WebSocketSession.

当将入站消息流和出站消息流组合在一起时,不需要检查连接是否打开,因为反应性流发出终止活动的信号。入站流接收完成或错误信号,出站流接收取消信号。

处理程序的最基本实现是处理入站流的实现。下面的示例显示了这样的实现:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono handle(WebSocketSession session) {
        return session.receive()            
                .doOnNext(message -> {
                    // ...                  
                })
                .concatMap(message -> {
                    // ...                  
                })
                .then();                    
    }
}
  访问入站消息流。
  每一条信息都要做些什么。
  执行使用消息内容的嵌套异步操作。
  返回aMono这在接收完成时就完成了。
  对于嵌套的异步操作,您可能需要调用message.retain()在使用池数据缓冲区的底层服务器上(例如,Netty)。否则,数据缓冲区可能会在您有机会读取数据之前释放。有关更多背景,请参见数据缓冲区和编解码器.

以下实现组合了入站流和出站流:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono handle(WebSocketSession session) {

        Flux output = session.receive()               
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .map(value -> session.textMessage("Echo " + value));    

        return session.send(output);                                    
    }
}
  处理入站消息流。
  创建出站消息,生成组合流。
  返回aMono在我们继续接收的时候,这还没有完成。

入站流和出站流可以是独立的,只有在完成时才能连接起来,如下例所示:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono handle(WebSocketSession session) {

        Mono input = session.receive()                                
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .then();

        Flux source = ... ;
        Mono output = session.send(source.map(session::textMessage)); 

        return Mono.zip(input, output).then();                              
    }
}
  处理入站消息流。
  发送出去的消息。
  加入流并返回Mono这在两个流结束时都会完成。

3.2.3.握手

与servlet堆栈相同

WebSocketHandlerAdapter出席WebSocketService。默认情况下,这是HandshakeWebSocketService,它对WebSocket请求执行基本检查,然后使用RequestUpgradeStrategy用于正在使用的服务器。目前,有内置的支持反应堆Netty,Tomcat,Jetty和安德托。

HandshakeWebSocketService公开一个sessionAttributePredicate属性,该属性允许设置PredicateWebSession并将它们插入WebSocketSession.

3.2.4.服务器竞争

与servlet堆栈相同

这,这个,那,那个RequestUpgradeStrategy对于每个服务器,都公开基础WebSocket引擎可用的与WebSocket相关的配置选项。以下示例在Tomcat上运行时设置WebSocket选项:

@Configuration
static class WebConfig {

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter(webSocketService());
    }

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

检查您的服务器的升级策略,看看有哪些选项可用。目前,只有Tomcat和Jetty公开了这些选项。

3.2.5.CORS

与servlet堆栈相同

配置CORS并限制对WebSocket端点的访问的最简单方法是让WebSocketHandler实施CorsConfigurationSource并返回一个CorsConfiguraiton包含允许的来源、标题和其他详细信息。如果不能这样做,还可以将corsConfigurations属性的SimpleUrlHandler若要根据URL模式指定CORS设置,请执行以下操作。如果两者都指定,则通过使用combine方法上CorsConfiguration.

3.2.6.客户

SpringWebFlux提供了一个WebSocketClient抽象与实现的反应堆Netty,Tomcat,Jetty,水下,和标准Java(即JSR-356)。

  Tomcat客户机实际上是标准Java客户机的扩展,在WebSocketSession处理以利用Tomcat特有的API来暂停接收消息以进行背压。

若要启动WebSocket会话,可以创建客户端的实例并使用其execute方法:

WebSocketClient client = new ReactorNettyWebSocketClient();

URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
        session.receive()
                .doOnNext(System.out::println)
                .then());

一些客户端,例如Jetty,实现了Lifecycle需要停止并启动才能使用它们。所有客户端都有与基础WebSocket客户端的配置相关的构造函数选项。

4.测试

在SpringMVC中也是如此

这,这个,那,那个spring-test模块提供模拟实现。ServerHttpRequestServerHttpResponse,和ServerWebExchange。看见弹簧网反应来讨论模拟对象。

WebTestClient构建在这些模拟请求和响应对象之上,以支持在没有HTTP服务器的情况下测试WebFlux应用程序。您可以使用WebTestClient也适用于端到端的集成测试。

5.反应库

spring-webflux取决于reactor-core并在内部使用它来组成异步逻辑,并提供对反应性流的支持。通常,WebFluxAPI会返回FluxMono(因为这些都是内部使用的),并且从宽地接受任何反应流。Publisher实现作为投入。使用Flux对决Mono这很重要,因为它有助于表示基数-例如,是否需要一个或多个异步值,这对于决策(例如,编码或解码HTTP消息时)是必不可少的。

对于带注释的控制器,WebFlux透明地适应应用程序选择的反应库。这是在ReactiveAdapterRegistry,它提供了对反应性库和其他异步类型的可插拔支持。注册中心内置了对rxjava和CompletableFuture,但你也可以注册其他人。

对于功能性API(如功能端点,WebClient),适用于WebFlux API的一般规则-FluxMono作为返回值和反应流Publisher作为投入。当Publisher,无论是自定义的还是来自另一个反应库,它只能作为一个具有未知语义(0.N)的流来处理。但是,如果语义已知,则可以用FluxMono.from(Publisher)而不是传递原始Publisher.

例如,给定一个Publisher那不是Mono,Jackson JSON消息编写器需要多个值。如果媒体类型意味着无限流(例如,application/json+stream),值是单独编写和刷新的。否则,值将被缓冲到一个列表中,并呈现为一个JSON数组。

Version 5.1.0.RELEASE
最后更新2018-09-21 06:51:06世界标准时间

------共同学习 

你可能感兴趣的:(SpringBoot,java,Spring)