WebFlux知识结构
写在开头
由于本人课程繁忙,加上项目开发,考研等事项,所以文章更新起来比较慢,如果你觉得我的文章写的和你胃口,请私信我,我会及时更新。
Index
- @Validated的意思的,进行Bean装备时进行数据范围,类型验证; 具体一点就是,有些Bean定义不仅定义了数据类型,还定义了数据范围; 此时若是打算构造一个这样的Bean就需要对传入的数据进行判断; 而Spring集成了这个判断过程,就是@Validated注解
需要的基础
Spring Framework
JDK 8.0+ 函数式编程
Netty
Reactor响应式编程
Spring WebFlux
Reactive Core(响应式核心)
HttpHandler
一个对于HTTP请求的略偏底层的协定; 规范了Tomcat, Jetty, Netty, Undertow, Servlet3.1等服务器。 旨在对于不同的Web服务器提供更加抽象统一的接口。
Netty
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
Undertow
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
Tomcat
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();
Jetty
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();
Servlet3.1
WebHandler API
基于HttpHandler,并提供稍微高级一些,且更加常用的Web通用API,使代码编写更加容易; 并构建更加具体的编程模型。 由多个部分组成"责任链式"的处理模式(和Netty的链式处理思想一样)。
Special Bean Types(特殊的Bean类型)
1. WebExceptionHandler
提供对于Filter链中各个Filter异常以及WebHandler异常的处理,数量0-N。
2. WebFilter
提供"拦截式"逻辑处理, 数量0-N。 在过滤链其余部分的前面或后面以及WebHandler的前面或后面进行拦截(就是Filter的意思)。
3. WebHandler
处理器,也是主要的业务处理部分(Spring MVC中的Controller)。
4. WebSessionManager
对于WebSession的一个管理器。
5. ServerCodecConfigurer
用于访问HttpMessageReader实例以解析表单数据和文件数据; 然后经由ServerWebExchange的方法把数据传递出去。
6. LocaleContextResolver
LocaleContext的解析器。
7. ForwardedHeaderTransformer
处理HTTP头部,包括提取和移除某些头部键值对,或者只移除。
Form Data
ServerWebExchange提供Mono<MultiValueMap<String, String>> getFormData()方法; 此方法用于访问表单数据。
Multipart Data
ServerWebExchange提供了Mono<MultiValueMap<String, Part>> getMultipartData()方法; 此方法用来访问Multipart数据。
DefaultServerWebExchange使用配置好的HttpMessageReader<MultiValueMap<String, Part>>把"multipart/form-data"的内容解析到MultiValueMap里去。
当下,Synchronoss NIO Multipart是唯一被支持的可以进行对multipart请求进行非阻塞解析的第三方库。
为了把multipart数据解析成流的形式,可以使用Flux作为HttpMessageReader的返回值以替换HttpMessageReader<MultiValueMap<String, Part>>。
例如,在一个注解Controller里面,使用@RequestPart意味着根据名字对于每个独立部分进行Map形式的访问时需要对整个multipart数据进行解析。
作为对比,可以使用@RequestBody把请求体解析进Flux而不必整合进MultiValueMap。
Forwarded Headers
如果请求经过代理服务器(比如Nginx),请求的主机地址,端口,和请求方式可能会被更改; 此时对于客户端来说,正确地找到主机地址等信息会是一个挑战。
ForwardedHeaderTransformer是一个组件类,提供基于请求头修改host,port,和方法的功能; 然后移除这些请求头,此类可以被声明成bean形式。
可以通过设置ForwardedHeaderTransformer的removeOnly=true来实现只移除而不访问某些不安全头部的效果。
Filters(过滤器)
在WebHandler API里面,你可以使用WebFilter去实现'拦截式'逻辑; 一个WebFilter可以拦截进入其他的WebFilter或者WebHandler或从其产生的数据流。
当使用WebFlux Config时,你会发现注册一个WebFilter就像把它声明为Spring Bean那样容易; 可选的,可以使用@Order注解或实现Ordered接口实现对WebFilter排序。
CORS(跨域访问)
Spring WebFlux通过控制器组件的注解对于CORS提供一个细粒度的支持。 然而,当你使用Spring Security时,我们推荐(不是我,是Spring官方)使用内置的CorsFilter类, 此类必须放在Spring Security的所有Filter的前面。
Exceptions(异常)
在WebHandler API里面,你可以使用WebExceptionHandler去处理来自WebFilter或WebHandler的异常。
当使用WebFlux Config时,注册一个WebExceptionHandler就像声明一个Spring Bean那样容易。
同样以可以使用@Order或实现Ordered接口实现设置前后顺序。
有两个可用的WebExceptionHandler实现,分别为: ResponseStatusExceptionHandler: 通过设置response的HTTP Code 来处理ResponseStatusExceptionHandler类型的异常。 WebFluxResponseStatusExceptionHandler: ResponseStatusExceptionHandler的继承类, 可以在任何异常里面决定@ResponseStatus注解的HTTP状态码。
Codecs(编/解码器)
spring-web和spring-core模块提供了对,通过拥有背压反应式流的非阻塞IO,从byte数据到高级对象的反序列化和高级对象到byte的序列化的支持。
如下描述了以上的支持:
Encoder和Decoder是底层协议,用于编/解码分离开的HTTP内容。
HttpMessageReader和HttpMessageWriter是用于编/解码HTTP 消息内容的编/解码器。
一个编码器可以被EncoderHttpMessageWriter封装使其可以合适的用于Web应用; 同样,解码器也可以被DecoderHttpMessageReader封装。
DataBuffer抽象并封装了不同服务器的byte buffer(诸如Netty的ByteBuf, java.nio.ByteBuffer); 这也是所有的编/解码器工作的数据依赖(或数据来源)。
spring-core模块提供了byte[],ByteBuffer,DataBuffer,Resource,和String的编码器以及解码器实现。
对于表单数据,二进制/文件内容,服务器发送的事件等; spring-web模块提供了包括Jackson JSON,Jackson Smile,JAXB2,Protocol Buffers的编/解码器以及只支持web的HTTP消息读/写器实现。
ClientCodecConfigurer和ServerCodecConfigurer通常用于配置和自定义Web应用程序里面的编/解码器。
Jackson JSON(杰克逊JSON)
Jackson2Decoder工作原理:
- Jackson是异步非阻塞的解析器, 用于把字节块流聚合到TokenBuffer的每个块中, 其中, 每一块代表一个JSON对象
- 每一个TokenBuffer都会传递到Jackson的ObjectMapper以创建一个更高等级的对象
- 当解码一个单值publisher(例如Mono), 就会产生一个TokenBuffer
- 当解码一个多值publisher(例如Flux), 一旦一个完整的对象接收到足够的byte, 每一个TokenBuffer都会被传递到ObjectMapper, 输入的对象可以是JSON数组, 如果Content-Type是"application/stream+json"的话, 那么也可以是按行分割的JSON
Jackson2Decoder目的: 将字节流解码为JSON并使用Jackson 2.9转换为Object的.
Jackson2Encoder工作原理:
- 对于单值publisher, 简单地通过ObjectMapper进行序列化就行
- 对于用"application/json"修饰的多值publisher, 默认情况下使用Flux.collectToList()把值集合起来然后系列化这个集合
- 对于流媒体类型(例如: application/stream+json, application/stream+x-jackson-smile)修饰的多值publisher, 使用基于行分隔符的JSON格式对每个值进行编码, 写出, 和刷新
- 对于SSE, 每个事件都会调用Jackson2Encoder, 执行的结果会被主动刷新以确保传输没有任何的延迟
Jackson2Encoder和Jackson2Decoder都不支持String以及String序列的编/解码, 如果需要从Flux得到JSON数组, 应该使用Flux.collectToList()并对Mono<List>进行编码
Form Data(表单数据)
FormHttpMessageReader和FormHttpMessageWriter支持编/解码"application/x-www-form-urlencoded"类型的内容
在经常需要从多个位置访问表单数据的服务端, ServerWebExchange提供了专用的getFormData()方法通过FormHttpMessageReader来解析内容然后把结果缓存起来以供多次访问
一旦getFormData()被调用, 请求体里面的原生数据就没法被再次访问了, 介于此, 应用应通过ServerWebExchange来访问被缓存的数据而不是访问原生的数据 P.s.其实吧, 我觉得这么设计可能是因为Netty的引用计数导致的
Multipart(文件数据/二进制数据)
MultipartHttpMessageReader和MultipartHttpMessageWriter提供对于"multipart/form-data"类型的数据的编/解码支持
实际上, MultipartHttpMessageReader通过代理另一个HttpMessageReader来完成实际的数据解析(把数据解析成Flux), 然后只是简单地把结果集合进一个MultiValueMap里去
当下, Synchronoss NIO Multipart被用来完成实际的解析
在经常需要从多个位置访问multipart数据的服务端, ServerWebExchange提供了专用的getMultipartData()方法通过MultipartHttpMessageReader来解析内容然后把结果缓存起来以供多次访问 (类似表单数据)
一旦getMultipartData()被调用, 请求体里面的原生数据就没法再次被访问, 基于此, 应用应当一致的使用getMultipartData()来完成对于parts的多次地, map形式地访问; 或者使用SynchronossPartHttpMessageReader来进行唯一一次的对于Flux的访问(之后数据就被销毁了) P.s.这么设计的原因同表单数据访问
Limits(限制)
把输入流的部分或全部数据整到缓冲区的Decoder和HttpMessageReader的实现类, 可以设置一个内存所能包含的最大字节数大小, 以避免爆内存
在某些情况下会发生缓冲区处理, 比如聚合输入的数据成一个对象(HTTP请求都是一段一段发送的, 所以要聚合); 或者分割输入流时, 也会发生缓冲
配置缓冲区最大大小时, 可以检查提供的Decoder或HttpMessageReader是否已经拥有了maxInMemorySize这个属性; 在WebFlux, 提供了一个单独的地方用来为所有的编解码器设置这个属性; 在客户端里, 可以通过WebClient.Builder设置此属性
对于Multipart类型的数据来说, 此限制只作用于非文件的部分; 对于文件部分, 它将决定文件写入硬盘的阀值; 对于写入到硬盘的文件部分来说, 有一个maxDiskUsagePerPart属性来限制每部分文件的硬盘空间的数量
同样, 有一个maxParts属性限制在一次Multipart请求中的文件部分总数量, 为了在WebFlux配置这三个属性, 需要向ServerCodecConfigurer提供一个预配置的MultipartHttpMessageReader实例
Streaming(流式)
流式传输到HTTP响应时, 应增加心跳检测机制, 以及时地断开连接
DataBuffer(数据缓冲组件)
对各种服务器的字节缓冲区进行了封装
对于Netty的引用计数, WebFlux一般不关心此问题, 除非直接消费或生产数据而不是依靠编/解码器进行和高级对象的转换(就是不走编解码器, 手动解析数据, 俗称'造轮子', 有可能会造成内存泄漏), 或者自己造编/解码器(也有可能会造成内存泄漏)
Logging(日志)
DispatcherHandler(分发处理器)
一个中央Handler, 也就是DispatcherHandler负责请求的转发, 实际的请求处理是被配置好的代理组件, 这种工作模式是灵活的, 且支持各种各样的工作流
DispatcherHandler通过Spring配置自动发现它需要的代理组件它也被设计成一个Spring Bean形式并通过实现ApplicationContextAware接口实现对它所在的上下文的访问
WebFlux的Spring配置通常包含:
- Bean名字为"webHandler"的DispatcherHandler
- WebFilter和WebExceptionHandler Beans
- DispatcherHandler的特殊的Bean
- 其他
配置通常传递给WebHttpHandlerBuilder来构建处理链
例如:
ApplicationContext context = ... HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();
Special Bean Types(特殊的Bean类型)
DispatcherHandler把请求委托给特殊的Bean来完成请求处理并返回适当的回复(response)
HandlerMapping
把一个请求映射到一个Handler上面去, 映射基于一些规范, 规范的细节由HandlerMapping的实现决定-基于注解的控制器, 简单URL模式映射, 和其他的
主要的HandlerMapping实现类是用于对@RequestMapping注解的方法的RequestMappingHandlerMapping, 对于函数端点路由的RouterFunctionMapping, 和对于URI路径模式的显式注册以及WebHandler实例的SimpleUrlHandlerMapping
HandlerAdapter
帮助DispatcherHandler对于某个请求调用已被映射的Handler而无关于此Handler实际上是怎么被调用的
例如, 调用一个带注解的控制器需要先解析注解, 而此类可以让DispatcherHandler免于此繁琐的细节处理
HandlerResultHandler
处理来自处理器调用的结果并最终确定回复响应(写出HttpResponse的意思)
WebFlux Config(WebFlux配置)
WebFlux Config是一个不错的配置方法
Processing(处理)
DispatcherHandler处理请求的工作流程:
- 每个HandlerMapping都会被唤醒以找到匹配的处理器, 第一个匹配的将会作为此次请求的处理器
- 一旦某个处理器被找到, 它将会被一个适当的HandlerAdapter执行, 此类会把执行结果(返回值)封装成HandlerResult
- HandlerResult会被传递到一个合适的HandlerResultHandler来进行视图渲染或直接写入到response里面去来完成此次执行
Result Handling(结果处理)
Handler调用结果会通过HandlerAdapter, 然后封装成一个HandlerResult, 并附加一些文本信息, 然后传递到第一个可以处理它的HandlerResultHandler来进行最终处理
以下是HandlerResultHandler的一些默认实现: ResponseEntityResultHandler ServerResponseResultHandler ResponseBodyResultHandler ViewResolutionResultHandler
ResponseEntityResultHandler
ResponseEntity, 通常来自@Controller实例。
ServerResponseResultHandler
ServerResponse, 通常来自功能端点
ResponseBodyResultHandler
来自@ResponseBody方法或@RestController类的返回值
ViewResolutionResultHandler
CharSequence, View, Model, Map, Rendering或任何其他Object都会被视为模型属性
Exceptions(异常)
处理器调用失败或通过HandlerResultHandler处理处理器返回值时失败都会触发特定的错误函数
只要发生错误, 错误函数就能更改响应状态
可以在选取一个处理器处理异常之前使用@ControllerAdvice处理异常
View Resolution(视图解析)
Annotated Controllers(注解控制器)
@Controller
即可以通过标准的SpringBean方法定义一个控制器Bean, 也可以通过@Controller注解构建
@Controller注解提供自动推断和路径扫描的方法来装配控制器组件, 可以让控制器类被识别成Web组件
@RestController是@Controller和@ResponseBody的组合注解, 意味着返回值直接写入到response body而不是进行视图渲染或写入HTML模板
Request Mapping(请求映射)
@RequestMapping注解常用来给控制器方法提供映射关系; 它拥有多种属性,来进行匹配, 比如通过URL匹配, 通过HTTP 请求方法匹配, 请求参数, 头部, 或媒体类型进行匹配
既可以在类层面使用此注解以表明对类的所有方法都囊括此路径映射, 也可以在方法层面使用来进行特定的请求处理
@RequestMapping有一些子注解, 来进行请求方式的限定: @GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping
URI Patterns(URI匹配)
URI映射的通配符的使用 ?: 匹配一个字符 *: 在一个路径片段里面匹配0或多个字符 **: 匹配0或多个路径片段
同样, 你也可以通过在路径里面设置变量来获取URI里面的值 例如: @GetMapping("/owners/{ownerId}/pets/{petId}") public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { // ... } 路径变量参数同样可用于类级别的映射
URI变量可以自动转换成合适的类型, 否则会抛出TypeMismatchException异常, 简单基本类型(诸如: int, double, long, String, Date等)都可以被直接获取
URI变量也可以被显式地命名(比如: @PathVariable("customId") )
语法{*varName}表示匹配0或多个余下的路径片段, 比如("/resources/{*path}")
语法{varName:正则表达式}可以使用正则表达式的方式完成匹配
URI还可以使用嵌入式的占位符${...}, 以在系统启动时通过属性的方式注入
WebFlux的URI不同于SpringMVC的一点就是, 不能使用后缀表达式
Pattern Comparison(模式比较)
当多个映射匹配一个URL时, 必须比较它们以发现最佳的匹配项; 这项工作由PathPattern.SPECIFICITY_COMPARATOR完成, 它负责寻找最佳匹配项
对于每个可匹配项, 会根据URI变量和占位符的数量计算得到一个值, URI变量的值比占位符小, 分数小的匹配, 若分数相同, 长的匹配
Consumable Media Types(可消费的媒体类型)
可以通过请求的Content-Type属性来细粒化请求匹配, 例如: @PostMapping(path = "/pets", consumes = "application/json") public void addPet(@RequestBody Pet pet) { // ... }
consumes属性同样支持否定形式, 例如: !text/plain意味着匹配所有非text/plain的请求
对于类层次的consumes属性, 当方法体使用了consumes属性时, 意味着会重写类的consumes属性
MediaType封装了一些类型
Producible Media Types(可产生的媒体类型)
可以根据请求的Accept属性细粒化请求映射, 比如: @GetMapping(path = "/pets/{petId}", produces = "application/json") @ResponseBody public Pet getPet(@PathVariable String petId) { // ... } 表示除了匹配URI之外, 还要匹配接受的返回类型为"application/json"的那个请求
媒体类型还能指定字符集, 同时也支持取反操作, (匹配只接受除了此类型之外的请求)
方法级别的同样可以覆写类级别的设置
Parameters and Headers(参数和请求头)
可以根据请求参数细粒化匹配: 1.根据某个参数是否存在进行匹配 2.根据某个参数是否不存在进行匹配 3.根据某个参数是否等于某个确定的值进行匹配 例如: @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) { // ... }
HTTP HEAD, OPTIONS
Custom Annotations(自定义注解)
Spring允许组合注解来实现请求映射。 旨在得到@RequestMapping的一个更加细粒化的子注解。
如果想获得更加细粒化的逻辑匹配。 继承RequestMappingHandlerMapping并覆写getCustomMethodCondition()方法。 这样可以检查自定义属性和返回你自己的请求条件(RequestCondition)
Explicit Registrations(显式地注册)
你可以程序化的注册处理器方法,这可以适用于动态注册或高级情形。 比如同一个处理器在不同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); // 添加注册
}
}
Handler Methods(处理器方法)
@RequestMapping拥有灵活的处理器方法签名; 可以从一系列已支持的方法参数和返回值里面选取合适的作为使用。
Method Arguments(方法参数)
在那些需要处理阻塞IO的参数上可以支持响应式类型(比如:Reactor,RxJava或其他的)。 不需要阻塞的参数不建议使用响应式类型
JDK8的Optional和带有required属性的注解搭配使用,等同于注解添上required=false
1. ServerWebExchange
完全访问ServerWebExchange。 ServerWebExchange是一个容器,包含了: HTTP Request和HTTP Response; request和session属性; checkNotModified方法和其他
2. ServerHttpRequest, ServerHttpResponse
添加HTTP request和response
3. WebSession
访问Session; 除非添加属性,否则不会强制开始一个新的session; 支持响应式类型。
4. java.security.Principal
当前经过身份验证的用户-可能是特定的Principal实现类(如果已知)。 支持响应式类型。
5. org.springframework.http.HttpMethod
请求的方法
6. java.util.Locale
当前的请求区域设置,由最具体的可用LocaleResolver确定; 实际上是配置的LocaleResolver/LocaleContextResolver。
7. java.util.TimeZone + java.time.ZoneId
与当前请求关联的时区,由LocaleContextResolver确定。
8. @PathVariable
用来访问URI模板变量
9. @MatrixVariable
用来访问URI路径片段里面的键值对
10. @RequestParam
用来访问Servlet请求参数; 参数的值已被转换成已声明的方法的参数类型。
注意:@RequestParam的使用是可选的
11. @RequestHeader
用来访问请求头; 请求头的值已经被转换成已声明的方法的参数类型。
12. @CookieValue
用来访问Cookie; Cookie的值已经被转换成已声明的方法的参数类型。
13. @RequestBody
用来访问HTTP请求体; 通过HttpMessageReader实例,请求体内容已经被转换成已声明的方法的参数类型。 支持响应式类型。
14. HttpEntity<B>
访问请求的头部和请求体; 请求体被HttpMessageReader实例转换成已声明的方法的参数类型。 支持响应式类型。
15. @RequestPart
用来访问"multipart/form-data"类型的请求的某个part。 支持响应式类型。
16. java.util.Map, org.springframework.ui.Model, and org.springframework.ui.ModelMap.
用于访问HTML控制器中使用的模型,并作为视图渲染的一部分呈现给模板。
17. @ModelAttribute
用于访问应用了数据绑定和验证的模型中的现有属性(如果不存在,则进行实例化)。
@ModelAttribute的使用是可选的。
18. Errors, BindingResult
用于访问命令对象的验证和数据绑定中的错误。
必须在经过验证的方法参数后立即声明Errors或BindingResult参数。
19. SessionStatus + class-level @SessionAttributes
用于标记表单处理完成; 将触发清除所有通过类级别@SessionAttributes注解声明的Session属性。
20. UriComponentsBuilder
用于准备相对于当前请求的Host,Port,Scheme和Path的URL。
21. @SessionAttribute
用于访问任何Session属性
22. @RequestAttribute
用于访问请求属性。
23. Any other argument
默认情况下,如果是简单类型,则解析为@RequestParam。
Return Values(返回值)
所有的返回值都支持响应式类型。
1. @ResponseBody
通过HttpMessageWriter编码返回值并写入到response里面去。
2. HttpEntity<B>, ResponseEntity<B>
返回值指定完整的响应,包括HTTP头部和正文; 然后经由HttpMessageWriter写入到HTTP response里面去。
3. HttpHeaders
返回一个有HTTP头部但是没有内容体的response。
4. String
返回成模板名称,会被视图解析器解析然后得到模板名。
5. View
返回一个视图。
6. java.util.Map, org.springframework.ui.Model
基于请求路径,隐式地确定视图名称; 属性被隐式地添加到模型数据。
7. @ModelAttribute
准备添加到模型数据里面去; 视图名从请求路径里面隐式地推断。
8. Rendering
用于模型和视图渲染方案的API。
9. void
返回值为空的方法,可能是异步的; 如果参数有ServerHttpResponse,ServerWebExchange,或@ResponseStatus注解; 那么表明此方法完全处理了响应,不需要返回值然后让Spring写出。
还有一种可能就是,没有ResponseBody,只有ResponseHeader。
10. Flux<ServerSentEvent>, Observable<ServerSentEvent>, or other reactive type
发送服务器事件; 仅需要写入数据时,可以省略ServerSentEvent包装器。 若想使用"text/event-stream"; 必须通过produces属性在映射中请求或声明"text/event-stream"。
11. Any other return value
Type Conversion
对于String形式的输入请求,如果声明的类型不是String;那么会发生强制类型转换。
除了基本类型的强制类型转换,还可以通过WebDataBinder; 或者通过FormattingConversionService注册Formatters来实现自定义转换。
Matrix Variables
矩阵变量,出现在URI中,其中每个变量之间用';'分隔;每个值之间用','分隔; 例如:"/cars;color=red,green;year=2012"; 其中有三个变量:[cars], [color], [year]; 其中[color]对应两个值: red, green。 多个值可以分开来对应一个变量; 比如: "color=red;color=green;color=blue"意味着color有三个值。
不同于Spring MVC,矩阵变量的缺省不影响请求映射; 如果你想访问矩阵变量,你可以在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<String, String> matrixVars,
@MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 22, "s" : 23]
}
@RequestParam
不同于Spring MVC,WebFlux的@RequestParam注解不会把查询参数,表单数据和文件/二进制数据绑定到一块; WebFlux的@RequestParam默认只有查询参数,因此,若想实现绑定,可以使用数据绑定把 查询参数,表单数据,二进制/文件数据变成命令对象;
默认情况下使用@RequestParam注解的方法参数是必须要传递的, 但是您可以通过将@RequestParam的required标志设置为false或通过使用java.util.Optional包装器声明参数来指定方法参数是可选的。
当在Map <String,String>或MultiValueMap <String,String>参数上声明@RequestParam注解时, 将使用所有查询参数填充Map。
此注解是可选的,意思是: 任何没有被其他参数解析的基本类型都会被视为使用@RequestParam注解修饰的。
@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
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding, // 获取接受的编码类型
@RequestHeader("Keep-Alive") long keepAlive) { // 获取是否保持连接
//...
}
同样,要是使用Map或MultiValueMap来充当注解的参数,那么也会自动填充Map。
@CookieValue
可以使用@CookieValue注解将HTTP cookie的值绑定到控制器中的方法参数上来。
@ModelAttribute
可以在方法参数前面加上@ModelAttribute注解来实现对模型数据的访问; 如果此数据不存在,就创建一个新的实例。 模型数据的值将会覆写查询参数和表单数据里面的同名变量。 这是因为数据绑定的存在,这将让你免于手动解析单个数据的困扰。
在@ModelAttribute后面添加BindingResult可以处理数据绑定时的异常。
通过添加@Valid注解在@ModelAttribute前面可以实现自动验证。
@ModelAttribute是可选的。
@SessionAttributes
此注解用于把两次请求之间的WebSession存储在Model里面。
它是类型级别的注解,用于声明特定控制器使用的Session属性。
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
以上代码会把pet存储在Session里面。
直到有别的控制器使用SessionStatus方法清空Session。
以下方法会清空Session
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors()) {
// ...
}
status.setComplete();
// ...
}
}
}
@SessionAttribute
如果需要访问全局管理(例如,在控制器外部(例如,通过过滤器))管理的并且可能存在或不存在的预先存在的会话属性, 则可以在方法参数上使用@SessionAttribute注解,例如以下示例显示:
@GetMapping("/")
public String handle(@SessionAttribute User user) { // 使用名为user的Session数据
// ...
}
考虑将WebSession注入控制器方法中,以实现添加或删除Session的功能。
@RequestAttribute
更简单地使用@SessionAttribute访问之前的请求属性; 可以考虑使用@RequestAttribute,如下所示:
@GetMapping("/")
public String handle(@RequestAttribute Client client) { // 访问之前的属性
// ...
}
Multipart Content
处理文件上传表单的最好方法就是通过数据绑定,绑定到命令对象上。 比如:
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
// ...
}
}
对于一次提交:
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) { // 访问文件数据
// ...
}
为了对原生数据进行反序列化,可以用具体的对象而不是Part进行数据的接收:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { // 反序列化为MetaData
// ...
}
同样可以组合@RequestPart和@Validated来实现验证; 然后使用Mono来准备处理异常
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
// use one of the onError* operators...
}
为了把所有的数据包装成MultiValueMap形式,可以使用@RequestBody注解
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) {
// ...
}
在流式数据中,为了连续的访问数据,可以使用Flux来进行处理
@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) {
// ...
}
@RequestBody
可以使用此注解读取HTTP请求体或反序列化为某个类; 比如:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
不同于Spring MVC,在Web Flux中,@RequestBody是一个支持响应式且完全非阻塞的设计; 同时也可以用流来读取,比如:
@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
// ...
}
可以使用@Validated来进行标准的Spring Bean验证 异常包含具有错误详细信息的BindingResult; 可以在控制器方法中通过使用异步包装器声明参数; 然后使用与错误相关的运算符来处理该异常。
HttpEntity
或多或少类似于@RequestBody,但是它同时拥有请求头和请求体。 比如:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@ResponseBody
此注解可以通过HttpMessageWriter把返回值序列化到response body里面去。 比如:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
类级别的@ResponseBody会被方法继承。
@ResponseBody支持异步,响应式操作; 意味着可以通过响应式类型把结果写入到response body里面去。
ResponseEntity
ResponseEntity就像@ResponseBody但是有状态码和返回头; 例如:
@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).build(body);
}
WebFlux支持异步地使用单值响应类型去生成ResponseEntity或; 单和多值响应类型来生成body部分。
Jackson JSON
1. JSON Views
Model(模型数据)
前后端分离,暂略不表。基本同Spring MVC。
DataBinder(数据绑定器)
适用于Spring没法自动转换数据的情况。
Managing Exceptions(管理异常)
REST API exceptions
Controller Advice(控制器建议)
Functional Points(函数式端点)
除了注解方法,还有函数式声明这一种方法来进行业务处理。
说明白点,就是HTTP请求会被handler处理,怎么处理呢?传递过来一个ServerRequest,handler接受,然后返回一个Mono。
处理器类似于@RequestMapping修饰的方法体。
注意,接收的数据和返回的数据类型是固定的,所以请不要随意更改。
请求由路由函数路由到相应的处理器方法,并带回一个Mono,所以,很明显了,路由函数类似@RequestMapping,但是是带有数据和(转发)行为的@RequestMapping。
RouterFunctions.route()提供了一系列工具方法来构建路由函数,然后利用RouterFunctions.toHttpHandler(RouterFunction)把路由函数转变成HttpHandler然后安装进内置的ServerAdapter。
HandlerFunction(处理器函数)
请求包含Flux或Mono形式的元素;响应以响应式Publisher呈现,包含Flux和Mono。
ServerRequest(服务器请求)
ServerRequest提供访问HTTP方法,URI, 报头,和查询参数的方法,同样,body()方法用于直接访问请求体。
把请求体提取到Mono:
Mono<String> string = request.bodyToMono(String.class);
把请求体提取到pojo:
Flux<Person> people = request.bodyToFlux(Person.class);
使用更加通用的ServerRequest.body(BodyExtractor)进行提取数据:
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
访问表单数据:
Mono<MultiValueMap<String, String> map = request.formData();
访问multipart数据:
Mono<MultiValueMap<String, Part> map = request.multipartData();
一次访问multipart数据的多个部分:
Flux<Part> parts = request.body(BodyExtractors.toParts());
ServerResponse(服务器响应)
ServerResponse提供了访问response体的方法,可以使用build()方法来创建。可以使用构建器来设置响应状态码,添加响应头,提供响应体等。
一个使用200状态码的例子:
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
取决于编解码器支持,可以通过提供隐式参数来表明响应体应该怎么被序列化和反序列化。例如:
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
Handler Classes(处理器类)
可以通过编写处理方法来实现对于请求的处理,比如:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");
如果需要写多个处理方法,那么可以考虑把他们组织到一个处理类里面,此类就像@Controller修饰的类。比如:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
return repository.getPerson(personId)
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
Validation(验证)
可以使用Spring Validation工具来对请求体进行验证。
HandlerFunction(路由器函数)
路由函数用于把请求路由到对应的处理函数;通常没必要自己写路由函数,RouterFunctions工具类提供了方便的方法。RouterFunctions.route()让你可以快速的构建一个路由函数,同样RouterFunctions.route(RequestPredicate, HandlerFunction)提供了一个更加直接的方式来创建。
推荐使用route()方法,因为它提供了更加‘捷径’的方法。除了基于HTTP方法的路由,还提供了附加的断言来精确化映射,对于每个HTTP方法,都有一个被重载的变种方法,提供一个RequestPredicate作为参数来进行附加的约束以此达到更加准确的映射匹配。
Predicates(断言)
RequestPredicates工具类基于请求路径,HTTP方法,Content-Type等,提供了常用的实现。比如下面的例子基于Accept进行划分:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
还可以组合你自己的断言:
RequestPredicate.and(RequestPredicate) // 同时匹配。
RequestPredicate.or(RequestPredicate) // 只要有一个匹配就行。
Routes(路由)
路由器路由原则:按顺序匹配,所以应该把更加具体的写在前面,通用一些的写在后面。 注意:这不同于注解方法,注解方法匹配原则是最佳匹配,所以要留意顺序。
通常,定义好的路由应该用RouterFunction组合在一起,有以下几种方式把路由组合到一块:
a) 在RouterFunctions.route()上使用add(RouterFunction)
b) 直接使用RouterFunction.and(RouterFunction)
c) RouterFunction.and()和RouterFunctions.route()的捷径组合-RouterFunction.andRoute(RequestPredicate, HandlerFunction)
举例:
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<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.add(otherRoute)
.build();
Nested Routes(嵌套路由)
多个路由函数分享同一个断言是很常见的事情,例如,共享请求路径。比如某共享路径的例子:
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
注意,path是一个消费route builder的消费者。 下面的例子展示了在依旧共享同一Accept属性时的嵌套情况:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("/person", handler::createPerson))
.build();
Running a Server(运行一个服务器)
运行路由函数的方法之一是:把路由函数转换成HttpHandler,然后使用Server Adapter来进行适配。
更加典型的做法,也是Spring Boot使用的方法,是使用WebFlux Config来设置一个基于DispatcherHandler的配置类。它使用Spring配置来声明处理请求所需要的组件。WebFlux的Java Configuration声明了以下基础组件来支持函数式端点。
RouterFunctionMapping: 推断Spring Configuration里面的多个RouterFunction<?> bean并通过RouterFunction.andOther来把他们组合在一起,然后把请求路由到组合好了的RouterFunction。
HandlerFunctionAdapter: 一个简单的适配器,可以让DispatcherHandler调用与请求匹配的HandlerFunction。
ServerResponseResultHandler: 调用ServerResponse的writeTo方法来把HandlerFunction的调用结果写入到response里面去。
处理组件让函数式端点更加适合生命周期内地处理DispatcherHandler请求,并同样可以与注解声明的控制器类并行运行。这也是Spring WebFlux启动器启用函数式端点的方式。
下面的例子展示了如何使用WebFlux Java配置:
@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...
}
}
Filtering Handler Functions(过滤处理器函数)
before作用于所有的路由器前面;after作用于所有的路由器后面;有嵌套便只作用于嵌套体内。