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.
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的其余部分,在那里契约是同步的(Filter
, Servlet
)或阻塞(getParameter
, getPart
)这是一个新的公共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。Stream
API,但不只是用于集合。这就是反应性库所扮演的角色。
反应器是SpringWebFlux的首选反应库。它提供了Mono
和Flux
用于处理0.1的数据序列的API类型(Mono
)和0.N(Flux
)通过与ReactiveX对齐的一组丰富的运算符运算符词汇。反应堆是一个反应流库,因此它的所有操作人员都支持无阻塞的反压力.反应堆非常关注服务器端的Java。它是与Spring密切合作开发的。
WebFlux需要将反应堆作为核心依赖项,但它可以通过反应性流与其他反应性库进行互操作。作为一般规则,WebFluxAPI接受普通的Publisher
作为输入,在内部将其调整为反应堆类型,并使用该类型,并返回Flux
或者是Mono
作为输出。所以,你可以通过任何Publisher
作为输入,您可以对输出应用操作,但是您需要调整输出,以便与另一个反应性库一起使用。只要可行(例如,带注释的控制器),WebFlux就会透明地适应RxJava或其他反应性库的使用。看见反应库更多细节。
1.4.编程模型
这,这个,那,那个spring-web
模块包含支持SpringWebFlux的反应性基础,包括HTTP抽象、反应性流适配器对于支持的服务器,编解码器,还有一个核心使用WebHandler
API可与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
直接。有关所有其他库,请参阅它们各自的文档。
这,这个,那,那个spring-web
模块包含构建反应性Web应用程序的抽象和基础结构。对于服务器端处理,这分为两个不同的级别:
HttpHandler:基本的、通用的HTTP请求处理API,具有非阻塞I/O和(反应性流)背压,以及每个受支持的服务器的适配器。
使用WebHandler
API:服务器请求处理的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.使用WebHandler
API
WebHandler api是一个通用的服务器web api,用于通过WebExceptionHandler
和WebFilter
组件和目标WebHandler
组件。你可以用WebHttpHandlerBuilder
通过向构建器添加组件或从Spring中检测组件ApplicationContext
。生成器返回使用HttpHandler
然后,您可以使用它在任何受支持的服务器上运行。
当HttpHandler
目标是跨HTTP服务器的最小契约,WebHandler
API提供了通常用于构建Web应用程序的基本特性。例如,ServerWebExchange
WebHandler API组件不仅提供了对请求和响应的访问,还提供了对请求和会话属性的访问,以及对解析的表单数据、多部分数据等的访问。
特殊豆类
下表列出了WebHttpHandlerBuilder
侦测:
豆名 | 豆型 | 数数 | 描述 |
---|---|---|---|
|
|
0..N |
链中的异常提供处理。 |
|
|
0..N |
将拦截样式逻辑应用于过滤链其余部分和目标的前后。 |
|
|
1 |
请求的处理程序。 |
|
|
0..1 |
经理 |
|
|
0..1 |
为获取 |
|
|
0..1 |
解析器 |
|
|
0..1 |
对于处理转发的类型标头,可以提取和删除它们,或者只删除它们。默认情况下不使用。 |
表格数据
ServerWebExchange
公开以下访问表单数据的方法:
Mono> getFormData();
这,这个,那,那个DefaultServerWebExchange
使用配置的HttpMessageReader
解析表单数据(application/x-www-form-urlencoded
)变成MultiValueMap
。默认情况下,FormHttpMessageReader
配置为由ServerCodecConfigurer
bean(参见WebHandler API).
多部分数据
与SpringMVC相同
ServerWebExchange
公开以下访问多部分数据的方法:
Mono> getMultipartData();
这,这个,那,那个DefaultServerWebExchange
使用配置的HttpMessageReader
解析multipart/form-data
内容转化为MultiValueMap
。目前,同步NIO多部分是唯一支持第三方库的库,也是我们所知道的用于多部分请求的非阻塞解析的惟一库。通过ServerCodecConfigurer
bean(参见WebHandler API).
若要以流方式解析多部分数据,可以使用Flux
从一个HttpMessageReader
相反。例如,在带注释的控制器中,使用@RequestPart
暗示Map
-类似于按名称访问各个部分,因此需要对多部分数据进行完整解析。相反,您可以使用@RequestBody
将内容解码为Flux
没有收集到MultiValueMap
.
转发标头
与SpringMVC相同
当请求通过代理(例如负载平衡器)时,主机、端口和方案可能会发生变化,因此从客户端的角度来看,创建指向正确主机、端口和方案的链接是一个挑战。
RFC 7239定义Forwarded
HTTP头,代理可以用来提供有关原始请求的信息。还有其他非标准的标头,包括X-Forwarded-Host
, X-Forwarded-Port
, X-Forwarded-Proto
, X-Forwarded-Ssl
,和X-Forwarded-Prefix
.
ForwardedHeaderTransformer
是一个组件,它根据转发的标头修改请求的主机、端口和方案,然后删除这些标头。您可以将其声明为名称为forwardedHeaderTransformer
,而且是检出用过了。
转发的标头需要考虑安全性,因为应用程序无法知道标头是由代理添加的,还是由恶意客户端添加的。这就是为什么应该将信任边界上的代理配置为删除来自外部的不受信任的转发通信量。您还可以配置ForwardedHeaderTransformer
带着removeOnly=true
,在这种情况下,它移除但不使用标头。
5.1ForwardedHeaderFilter 不受欢迎并被ForwardedHeaderTransformer 因此,在创建交换之前,可以更早地处理转发的标头。如果无论如何配置了筛选器,则将其从筛选器列表中删除,并且ForwardedHeaderTransformer 取而代之的是。 |
1.9.3.滤光片
与SpringMVC相同
在.。使用WebHandler
API,您可以使用WebFilter
在过滤器和目标处理链的其余部分之前和之后应用拦截样式逻辑。WebHandler
。当使用WebFlux Config,注册WebFilter
简单到将其声明为Springbean,并(可选)使用@Order
关于bean声明或通过实现Ordered
.
CORS
与SpringMVC相同
SpringWebFlux通过控制器上的注释为CORS配置提供了细粒度的支持。但是,当您将它与SpringSecurity一起使用时,我们建议依赖内置的CorsFilter
,必须在Spring Security的过滤器链之前订购。
见CORS而CORSWebFilter
更多细节。
1.9.4.例外
与SpringMVC相同
在.。使用WebHandler
API,您可以使用WebExceptionHandler
的链中的异常。WebFilter
实例和目标WebHandler
。当使用WebFlux Config,注册WebExceptionHandler
简单到将其声明为Springbean,并(可选)使用@Order
关于bean声明或通过实现Ordered
.
下表描述了可用的WebExceptionHandler
实现:
异常处理程序 | 描述 |
---|---|
|
提供对类型异常的处理。 |
|
延展 属性中声明此处理程序。WebFlux Config. |
1.9.5.编解码器
与SpringMVC相同
HttpMessageReader
和HttpMessageWriter
是编码和解码HTTP请求和响应内容的契约,通过非阻塞I/O与(有效率的流)背压。
Encoder
和Decoder
是编码和解码内容的契约,独立于HTTP。它们可以用EncoderHttpMessageWriter
或DecoderHttpMessageReader
并用于Web处理。
所有编解码器都是客户端或服务器端使用的。都是建立在DataBuffer
,它抽象字节缓冲区表示,例如NettyByteBuf
或java.nio.ByteBuffer
(见数据缓冲区和编解码器了解更多细节)。ClientCodecConfigurer
和ServerCodecConfigurer
通常用于配置和自定义要在应用程序中使用的编解码器。
这,这个,那,那个spring-core
模块有编码器和解码器byte[]
, ByteBuffer
, DataBuffer
, Resource
,和String
。这,这个,那,那个spring-web
模块为Jackson JSON、Jackson Say、JAXB 2、协议缓冲区和其他特定于Web的HTTP消息读取器和作者添加用于表单数据、多部分请求和服务器发送事件的编码器和解码器。
使用Jackson
解码器依赖于Jackson的非阻塞字节数组解析器将字节块流解析为TokenBuffer
流,然后可以将其转换为带有Jackson‘s的对象。ObjectMapper
。JSON和微笑当前支持(二进制JSON)数据格式。
编码器处理Publisher>
,如下:
如果Publisher
是Mono
(即单个值),该值在可用时被编码。
如果媒体类型是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-stream
, application/stream+json
)或否(例如,application/json
).
当流到HTTP响应时,不管媒体类型如何(例如,text/event-stream
和application/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相同
DEBUG
和TRACE
日志可以记录敏感信息。这就是表单参数和标头在默认情况下被屏蔽的原因,并且必须显式地启用它们的全部日志记录。
下面的示例演示了如何针对服务器端请求这样做:
@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();
DispatcherHandler
与SpringMVC相同
SpringWebFlux,类似SpringMVC,是围绕前端控制器模式设计的,其中一个中心WebHandler
,DispatcherHandler
,为请求处理提供共享算法,而实际工作则由可配置的委托组件执行。该模型具有灵活性,支持多种工作流。
DispatcherHandler
从Spring配置中发现它需要的委托组件。它也被设计成Springbean本身并实现ApplicationContextAware
以访问其运行的上下文。如果DispatcherHandler
的bean名称声明为webHandler
,反过来,它是由WebHttpHandlerBuilder
,它将一个请求处理链组合在一起,如使用WebHandler
API.
WebFlux应用程序中的Spring配置通常包含:
DispatcherHandler
用豆子的名字,webHandler
WebFilter
和WebExceptionHandler
豆子
DispatcherHandler
特殊豆类
其他
配置给WebHttpHandlerBuilder
要构建处理链,如下例所示:
ApplicationContext context = ...
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context);
结果HttpHandler
已准备好与服务器适配器.
1.10.1.特殊豆类
与SpringMVC相同
这,这个,那,那个DispatcherHandler
委托特殊bean处理请求并呈现适当的响应。我们所说的“特殊bean”指的是Spring管理的Object
实现WebFlux框架契约的实例。这些通常带有内置契约,但您可以自定义它们的属性、扩展它们或替换它们。
下表列出了DispatcherHandler
。请注意,在较低级别上还检测到了其他bean(请参见特殊豆类在WebHandler API中)。
豆型 | 解释 |
---|---|
|
将请求映射到处理程序。映射是基于一些标准,其细节取决于 主 |
|
帮助 |
|
处理程序调用的结果并最后确定响应。看见结果处理. |
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:
结果处理器类型 | 返回值 | 默认命令 |
---|---|---|
|
|
0 |
|
|
0 |
|
句柄返回值 |
100 |
|
另见视图解析. |
|
1.10.5.例外
与SpringMVC相同
这,这个,那,那个HandlerResult
从HandlerAdapter
可以根据特定于处理程序的机制公开用于错误处理的函数。如果以下情况下调用此错误函数:
处理程序(例如,@Controller
)调用失败。
处理程序返回值的过程。HandlerResultHandler
失败了。
错误函数可以更改响应(例如,错误状态),只要错误信号发生在从处理程序返回的响应类型生成任何数据项之前。
这就是为什么@ExceptionHandler
方法@Controller
支持类。相比之下,SpringMVC中同样的支持是建立在HandlerExceptionResolver
。一般来说,这并不重要。但是,请记住,在WebFlux中,不能使用@ControllerAdvice
若要处理在选择处理程序之前发生的异常,请执行以下操作。
另见管理异常在“附加说明的主计长”一节中,或例外在WebHandler API部分中。
1.10.6.视图解析
与SpringMVC相同
视图解析允许使用HTML模板和模型向浏览器呈现,而不需要将您绑定到特定的视图技术。在SpringWebFlux中,通过专用的HandlerResultHandler用ViewResolver
实例将字符串(表示逻辑视图名称)映射到View
举个例子。这,这个,那,那个View
然后用于呈现响应。
装卸
与SpringMVC相同
这,这个,那,那个HandlerResult
传入ViewResolutionResultHandler
包含来自处理程序的返回值和包含在请求处理过程中添加的属性的模型。返回值作为下列之一处理:
String
, CharSequence
*要解析为View
通过配置的列表ViewResolver
实现。
void
:根据请求路径选择默认视图名称,减去前导斜杠和尾斜杠,并将其解析为View
。当未提供视图名称(例如,返回模型属性)或异步返回值(例如,Mono
已完成)。
渲染用于视图解析场景的API。使用代码完成来探索IDE中的选项。
Model
, Map
要添加到请求的模型中的额外模型属性。
任何其他:任何其他返回值(简单类型除外,由BeanUtils#isSimpleProperty)被视为要添加到模型中的模型属性。属性名是从类名派生的。公约,除非处理程序方法@ModelAttribute
注释是存在的。
模型可以包含异步的、反应性的类型(例如,来自反应堆或RxJava)。在渲染之前,AbstractView
将这些模型属性解析为具体的值并更新模型。单值反应性类型解析为单个值或无值(如果为空),而多值反应性类型(例如,Flux
)被收集并解析为List
.
要配置视图解析,只需添加ViewResolutionResultHandler
bean到您的Spring配置。WebFlux Config提供用于视图解析的专用配置API。
看见视图技术有关与SpringWebFlux集成的视图技术的更多信息。
重定向
与SpringMVC相同
特辑redirect:
视图名称中的前缀允许您执行重定向。这,这个,那,那个UrlBasedViewResolver
(和子类)将此识别为需要重定向的指令。视图名称的其余部分是重定向URL。
净效果与控制器返回的RedirectView
或Rendering.redirectTo("abc").build()
,但现在控制器本身可以根据逻辑视图名称进行操作。视图名,如redirect:/some/resource
相对于当前应用程序,而视图名(如redirect:http://example.com/arbitrary/path
重定向到绝对URL。
内容协商
与SpringMVC相同
ViewResolutionResultHandler
支持内容协商。它将请求媒体类型与每个选定的媒体类型进行比较。View
。第一View
它支持请求的媒体类型。
为了支持诸如JSON和XML等媒体类型,SpringWebFlux提供了HttpMessageWriterView
,这是一个特别的View
通过HttpMessageWriter。通常,您可以通过WebFlux配置。如果默认视图与请求的媒体类型匹配,则始终选择并使用它们。
与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组件的角色。
若要启用自动检测以下内容,请执行以下操作:@Controller
bean,您可以将组件扫描添加到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
已经长大了。简单类型(int
, long
, Date
默认情况下支持等等,您可以注册对任何其他数据类型的支持。看见类型转换和使用DataBinder
.
URI变量可以显式命名(例如,@PathVariable("customId")
),但如果名称相同,则可以忽略该细节,并使用调试信息或-parameters
Java 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使用PathPattern 而PathPatternParser 对于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_VALUE 和APPLICATION_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_VALUE , APPLICATION_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
.
控制器方法参数 | 描述 |
---|---|
|
获得全部 |
|
访问HTTP请求或响应。 |
|
进入会议。除非添加属性,否则不会强制开始新会话。支持反应性类型。 |
|
当前通过身份验证的用户-可能是特定的 |
|
请求的HTTP方法。 |
|
当前请求区域设置,由最特定的 |
|
与当前请求关联的时区,由 |
|
用于访问URI模板变量。看见URI模式. |
|
用于访问URI路径段中的名称-值对。看见矩阵变量. |
|
用于访问servlet请求参数。参数值转换为声明的方法参数类型。看见使用 请注意,使用 |
|
用于访问请求标头。标头值转换为声明的方法参数类型。看见使用 |
|
为了获得饼干。Cookie值转换为声明的方法参数类型。看见使用 |
|
用于访问HTTP请求主体。将主体内容转换为声明的方法参数类型。 |
|
用于访问请求头和主体。身体被转换成 |
|
中的某个部分。 |
|
用于访问HTML控制器中使用的模型,并作为视图呈现的一部分公开给模板。 |
|
用于访问模型中的现有属性(如果不存在实例化的话),并应用数据绑定和验证。看见使用 请注意,使用 |
|
用于访问命令对象的验证和数据绑定中的错误(即 |
|
用于标记表单处理完成,这将触发对通过类级别声明的会话属性的清理。 |
|
用于准备相对于当前请求的主机、端口、方案和路径的URL。看见URI链接. |
|
对于任何会话属性的访问-与存储在会话中的模型属性相反,这是类级别的结果。 |
|
用于访问请求属性。看见使用 |
任何其他论点 |
如果方法参数与上述任何参数不匹配,则默认情况下,它被解析为 |
返回值
与SpringMVC相同
下表显示了支持的控制器方法返回值。注意,来自诸如反应堆、RxJava等库的反应性类型,或其他通常支持所有返回值。
控制器方法返回值 | 描述 |
---|---|
|
返回值通过 |
|
返回值指定完整响应,包括HTTP头,主体通过 |
|
用于返回带有标头而不带body的响应。 |
|
要用 |
|
A |
|
要添加到隐式模型中的属性,视图名称是根据请求路径隐式确定的。 |
|
要添加到模型中的属性,视图名称根据请求路径隐式确定。 请注意 |
|
用于模型和视图呈现场景的API。 |
|
一种具有 如果上述任何一个都不是真的,则 |
|
发出服务器发送的事件。这,这个,那,那个 |
任何其他返回值 |
如果返回值与上述任何内容不匹配,则默认情况下,它被视为视图名称(如果是的话)。 |
类型转换
与SpringMVC相同
一些表示基于字符串的请求输入的带注释的控制器方法参数(例如,@RequestParam
, @RequestHeader
, @PathVariable
, @MatrixVariable
,和@CookieValue
)如果参数声明为String
.
在这种情况下,类型转换将根据配置的转换器自动应用。默认情况下,简单类型(例如int
, long
, Date
)得到支持。类型转换可以通过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
默认情况下需要注释,但可以通过设置@RequestParam
到false
或者用java.util.Optional
包装纸。
如果目标方法参数类型不是String
。看见[mvc-ann-type econversion].
当@RequestParam
注释声明在Map
或MultiValueMap
参数时,映射将使用所有查询参数填充。
请注意,使用@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-Encoding
和Keep-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
注释用于Map
, MultiValueMap
,或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不同,它支持模型中的反应性类型-例如,Mono
或io.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
)回应。或者,您可以通过Errors
或BindingResult
参数,如以下示例所示:
@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
。用于@ResponseBody
或ResponseEntity
控制器方法,您可以使用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不同,它显式地支持模型中的反应性类型(例如,Mono
或io.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
或春天Converter
和Formatter
组件。此外,还可以使用WebFlux Java配置注册Converter
和Formatter
全局共享类型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。
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。手功能
ServerRequest
和ServerResponse
是提供JDK 8友好访问HTTP请求和响应的不可变接口。反应流反压力要求和响应体流。请求体用反应堆表示。Flux
或Mono
。响应体用任何反应流表示。Publisher
,包括Flux
和Mono
。有关这方面的更多信息,请参见反应库.
使用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
*检测一个或多个RouterFunction>
Spring配置中的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
本质上是一个函数,它接受ServerRequest
和HandlerFunction
并返回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 . |
与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
。一起,UriBuilderFactory
和UriBuilder
根据共享配置(如基本URL、编码首选项和其他详细信息),提供一种可插入的机制来从URI模板构建URI。
您可以配置RestTemplate
和WebClient
带着UriBuilderFactory
自定义URI的准备。DefaultUriBuilderFactory
的默认实现。UriBuilderFactory
用UriComponentsBuilder
内部并公开共享配置选项。
下面的示例演示如何配置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")
这,这个,那,那个WebClient
而RestTemplate
方法在内部展开和编码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.
与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
).
结合全局和本地配置的规则通常是附加的-例如,所有全局和所有本地源。对于只能接受单个值的属性,如allowCredentials
和maxAge
,本地重写全局值。看见CorsConfiguration#combine(CorsConfiguration)
更多细节。
若要从源中了解更多信息或进行高级自定义,请参见:
|
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配置来实现这一点。
默认情况下,全局配置支持以下功能:
所有的起源。
所有标题。
GET
, HEAD
,和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);
}
与SpringMVC相同
这,这个,那,那个弹簧安全项目提供了对保护Web应用程序免受恶意攻击的支持。请参阅Spring安全参考文档,包括:
WebFlux安全
WebFlux测试支持
CSRF保护
安全响应头
与SpringMVC相同
在SpringWebFlux中使用视图技术是可插拔的。无论您决定使用Thymeleaf、FreeMarker还是其他视图技术,都主要是一个配置更改的问题。本章介绍与SpringWebFlux集成的视图技术。我们假设你已经很熟悉视图解析.
1.16.1。齐米列夫
与SpringMVC相同
Thymeleaf是一个现代的服务器端Java模板引擎,它强调可以通过双击在浏览器中预览的自然HTML模板,这对于不需要运行服务器的UI模板(例如,设计人员)的独立工作非常有用。Thymeleaf提供了一套广泛的特性,并且它是积极开发和维护的。有关更完整的介绍,请参见齐米列夫项目主页。
Thymeleaf与SpringWebFlux的集成由Thymeleaf项目管理。该配置涉及一些bean声明,例如SpringResourceTemplateResolver
, SpringWebFluxTemplateEngine
,和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脚本模板 |
科特林 |
集成任何其他脚本引擎的基本规则是,它必须实现ScriptEngine 和Invocable 接口。 |
所需
与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相同
您可以声明ScriptTemplateConfigurer
bean指定要使用的脚本引擎、要加载的脚本文件、调用什么函数来呈现模板,等等。下面的示例使用了穆斯塔赫模板和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
,如Jackson2JsonEncoder
, Jackson2SmileEncoder
,或Jaxb2XmlEncoder
.
与其他视图技术不同的是,HttpMessageWriterView
不需要ViewResolver
但却是配置作为默认视图。您可以配置一个或多个这样的默认视图,包装不同。HttpMessageWriter
实例或Encoder
实例。与请求的内容类型匹配的内容在运行时使用。
在大多数情况下,模型包含多个属性。若要确定要序列化哪一个,可以配置HttpMessageWriterView
具有要用于呈现的模型属性的名称。如果模型只包含一个属性,则使用该属性。
与SpringMVC相同
HTTP缓存可以显著提高Web应用程序的性能。HTTP缓存围绕Cache-Control
响应头和随后的条件请求头,如Last-Modified
和ETag
. Cache-Control
建议私有(例如浏览器)和公共(例如,代理)缓存如何缓存和重用响应。阿ETag
报头用于发出条件请求,如果内容没有更改,则可能导致304(NOT_MODIAD)没有正文。ETag
可以看作是对Last-Modified
头球。
本节介绍SpringWebFlux中可用的HTTP缓存相关选项。
1.17.1. CacheControl
与SpringMVC相同
CacheControl
控件相关的设置提供支持。Cache-Control
标题,并在许多地方被接受为参数:
控制器
静态资源
当RFC 7234控件的所有可能指令。Cache-Control
响应头,CacheControl
type采用了一种面向用例的方法,该方法侧重于常见的场景,如下面的示例所示:
// 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缓存的显式支持。我们建议这样做,因为lastModified
或ETag
在将资源与条件请求头进行比较之前,需要计算资源的值。控制器可以添加ETag
和Cache-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)响应。否则,ETag
和Cache-Control
标题被添加到响应中。
还可以对控制器中的条件请求标头进行检查,如下面的示例所示:
@RequestMapping
public String myHandleMethod(ServerWebExchange exchange, Model model) {
long eTag = ...
if (exchange.checkNotModified(eTag)) {
return null;
}
model.addAttribute(...);
return "myViewName";
}
具体应用计算。 | |
响应已设置为304(NOT_MODATED)。没有进一步的处理。 | |
继续处理请求。 |
有三个变体用于检查有条件的请求。eTag
价值,lastModified
价值观,或者两者兼而有之。有条件的GET
和HEAD
请求时,可以将响应设置为304(NOT_MODATED)。有条件的POST
, PUT
,和DELETE
,您可以将响应设置为409(PREATION_FLOCK),以防止并发修改。
1.17.3。静态资源
与SpringMVC相同
应该使用Cache-Control
和条件响应报头以获得最佳性能。请参阅有关配置的一节。静态资源.
与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相同
默认情况下,格式化程序用于Number
和Date
类型,包括对@NumberFormat
和@DateTimeFormat
注释。如果类路径上存在Joda-time,也会安装对Joda-Time格式库的完全支持。
下面的示例演示如何注册自定义格式化程序和转换器:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
看见FormatterRegistrar SPI而FormattingConversionServiceFactoryBean 有关何时使用FormatterRegistrar 实现。 |
1.18.4。验证
与SpringMVC相同
默认情况下,如果bean验证存在于类路径(例如Hibernate Validator)中,LocalValidatorFactoryBean
注册为验证器用于@Valid
和Validated
在……上面@Controller
方法参数
在Java配置中,您可以自定义全局Validator
实例,如下面的示例所示:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public Validator getValidator(); {
// ...
}
}
请注意,您也可以注册Validator
本地实现,如以下示例所示:
@Controller
public class MyController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());
}
}
如果你需要一个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依赖于请求路径的解析表示形式,称为 SpringWebFlux也不支持后缀模式匹配,与SpringMVC不同,在SpringMVC中我们也支持推荐不再依赖它。 |
1.18.10。高级配置模式
与SpringMVC相同
@EnableWebFlux
进口品DelegatingWebFluxConfiguration
这一点:
为WebFlux应用程序提供默认的Spring配置
检测并委托给WebFluxConfigurer
实现来自定义该配置。
对于高级模式,可以删除@EnableWebFlux
直接从DelegatingWebFluxConfiguration
而不是实现WebFluxConfigurer
,如以下示例所示:
@Configuration
public class WebConfig extends DelegatingWebFluxConfiguration {
// ...
}
可以将现有方法保存在WebConfig
,但是您现在也可以重写基类中的bean声明,并且仍然有许多其他声明。WebMvcConfigurer
类路径上的实现。
与SpringMVC相同
支持HTTP/2需要Servlet 4容器,SpringFramework 5与ServletAPI 4兼容。从编程模型的角度来看,应用程序不需要做任何具体的事情。但是,有一些与服务器配置相关的注意事项。有关详细信息,请参阅http/2 wiki页面.
目前,SpringWebFlux不支持Netty的HTTP/2。也不支持以编程方式将资源推送到客户端。
SpringWebFlux包括一个反应性的、非阻塞的WebClient
对于HTTP请求。客户端具有功能良好的api,具有声明式组合的反应性类型,请参阅反应库。WebFlux客户端和服务器依赖于相同的非阻塞。编解码器若要对请求和响应内容进行编码和解码,请执行以下操作。
内部WebClient
委托给HTTP客户端库。默认情况下,它使用反应堆Netty,对Jetty有内置的支持。反应性HtpClient,而其他的则可以通过ClientHttpConnector
.
创建WebClient
是通过一种静态工厂方法:
WebClient.create()
WebClient.create(String baseUrl)
以上方法使用反应堆Netty。HttpClient
使用默认设置和Expectio.projectreactor.netty:reactor-netty
在类路径上。
您也可以使用WebClient.builder()
还有其他选择:
uriBuilderFactory
*定制UriBuilderFactory
作为基URL使用。
defaultHeader
*每个请求的标题。
defaultCookie
*每次请求都提供饼干。
defaultRequest
: Consumer
定制每个请求。
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
创建自己的资源(Executor
, ByteBufferPool
, Scheduler
),它们一直处于活动状态,直到进程退出或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 . |
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.BadRequest
, WebClientResponseException.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);
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() ,您必须始终使用body 或toEntity 方法ClientResponse 以确保资源被释放,并避免HTTP连接池的潜在问题。你可以用bodyToMono(Void.class) 如果没有预期的响应内容。但是,如果响应有内容,则连接将关闭,并且不会被放回池中。 |
请求体可以从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-urlencoded
被FormHttpMessageWriter
。下面的示例演示如何使用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-Type
到multipart/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);
您可以注册一个客户端过滤器(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
您还可以复制现有的WebClient
插入新筛选器,或删除已注册的筛选器。下面的示例在索引0处插入基本身份验证筛选器:
// static import of ExchangeFilterFunctions.basicAuthentication
WebClient client = webClient.mutate()
.filters(filterList -> {
filterList.add(0, basicAuthentication("user", "password"));
})
.build();
若要测试使用WebClient
,您可以使用模拟web服务器,例如OKHttp MockWebServer..若要查看其使用示例,请检查WebClientIntegrationTests
在Spring框架测试或static-server
OkHttp存储库中的示例。
与servlet堆栈相同
参考文档的这一部分涵盖了对反应性堆栈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-Protocol
HTTP握手请求的标头。在没有这一点的情况下,他们需要有自己的惯例。
3.1.2.何时使用WebSocket
WebSocket可以使网页具有动态和交互性。但是,在许多情况下,Ajax和HTTP流或长轮询相结合可以提供简单有效的解决方案。
例如,新闻、邮件和社交提要需要动态更新,但每隔几分钟就可以这样做。另一方面,合作、游戏和金融应用需要更接近实时。
仅仅延迟并不是一个决定性因素。如果消息量相对较低(例如,监视网络故障),HTTP流或轮询可以提供有效的解决方案。正是低延迟、高频率和高容量的结合,才是WebSocket使用的最佳案例。
还请记住,在Internet上,超出您控制范围的限制性代理可能会阻止WebSocket交互,因为它们不是配置为传递Upgrade
标头,或者是因为它们关闭了看似空闲的长寿命连接。这意味着对防火墙内部应用程序使用WebSocket比对面向公共的应用程序使用WebSocket要简单得多。
与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 方法 |
描述 |
---|---|
|
提供对入站消息流的访问,并在连接关闭时完成。 |
|
获取传出消息的源,写入消息,并返回 |
A 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
属性,该属性允许设置Predicate
从WebSession
并将它们插入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客户端的配置相关的构造函数选项。
在SpringMVC中也是如此
这,这个,那,那个spring-test
模块提供模拟实现。ServerHttpRequest
, ServerHttpResponse
,和ServerWebExchange
。看见弹簧网反应来讨论模拟对象。
WebTestClient
构建在这些模拟请求和响应对象之上,以支持在没有HTTP服务器的情况下测试WebFlux应用程序。您可以使用WebTestClient
也适用于端到端的集成测试。
spring-webflux
取决于reactor-core
并在内部使用它来组成异步逻辑,并提供对反应性流的支持。通常,WebFluxAPI会返回Flux
或Mono
(因为这些都是内部使用的),并且从宽地接受任何反应流。Publisher
实现作为投入。使用Flux
对决Mono
这很重要,因为它有助于表示基数-例如,是否需要一个或多个异步值,这对于决策(例如,编码或解码HTTP消息时)是必不可少的。
对于带注释的控制器,WebFlux透明地适应应用程序选择的反应库。这是在ReactiveAdapterRegistry
,它提供了对反应性库和其他异步类型的可插拔支持。注册中心内置了对rxjava和CompletableFuture
,但你也可以注册其他人。
对于功能性API(如功能端点,WebClient
),适用于WebFlux API的一般规则-Flux
和Mono
作为返回值和反应流Publisher
作为投入。当Publisher
,无论是自定义的还是来自另一个反应库,它只能作为一个具有未知语义(0.N)的流来处理。但是,如果语义已知,则可以用Flux
或Mono.from(Publisher)
而不是传递原始Publisher
.
例如,给定一个Publisher
那不是Mono
,Jackson JSON消息编写器需要多个值。如果媒体类型意味着无限流(例如,application/json+stream
),值是单独编写和刷新的。否则,值将被缓冲到一个列表中,并呈现为一个JSON数组。
Version 5.1.0.RELEASE
最后更新2018-09-21 06:51:06世界标准时间
------共同学习