跳到主要内容

Spring WebFlux

基本介绍

学习文档

  1. 官方文档
  2. 中文文档

WebFlux的特点

  • 完全非阻塞
  • 支持响应式流的背压
  • 在诸如 Netty、Undertow 和 Servlet 容器之类的服务器上运行

为什么需要 WebFlux?

  • 用一个非阻塞 web stack 来处理少量线程的并发,并以较少的硬件资源进行扩展。
  • 函数式编程

WebFlux 支持两种编程模式

  1. 基于注解的响应式编程
  2. 函数式路由和处理

Reactive Streams

Reactive Streams 的介绍 :Reactive Streams 是一种处理异步数据流的标准。

backpressure 背压

什么是背压

背压(backpressure):是一种流控制机制,允许消费者控制生产者的数据生成速度,以防止在数据生产速度超过消费速度时,消费者被压垮。

  • Producer 生产者
  • Consumer 消费者
  • Subscription 生产者和消费者的一种契约,消费者可以通过它来控制生产者的数据生成速度。

背压的实现

  1. 消费者准备好接收数据时,会创建一个 Subscription ,并将其提供给生产者。这个 Subscription 包含了消费者可以接收的数据量。
  2. 生产者接收到了 Subscription 后,开始生成数据,但它只会生成和发送消费者指定的数据量。
  3. 当消费者处理完这些数据后,它会更新 Subscription 中的数据量,并将更新后的 Subscription 传递给生产者。生产者会根据新的数据量生成和发送数据。
  4. 这个过程会一直重复,直到消费者不再需要数据,或者生产者无法生成更多的数据。

背压与负反馈的区别?

负反馈(英语:negative feedback),是反馈的一种。是指系统的输出会影响系统的输入,在输出变动时,所造成的影响恰和原来变动的趋势相反; 反之,就称为正反馈。另一种说法是系统在一个条件变化时,系统会作出抵抗该变化的行为、变动持续减少。例如人的体温上升时会流汗,流汗会散热使体温下降,就是负反馈的一个例子;

背压是一种在数据流处理中的控制机制,主要用于处理生产者和消费者处理数据速度不匹配的问题。 消费者可以通过背压控制生产者的数据生成速度,防止在数据生产速度超过消费速度时,消费者被压垮。 背压主要关注的是数据流的速度控制。

负反馈是一种在控制系统中的控制机制,主要用于处理系统的稳定性问题。 负反馈通过将系统的输出与系统的输入进行比较,然后根据比较结果对系统的输入进行调整,使系统的输出更加稳定。 负反馈主要关注的是系统的稳定性。

Spring WebFlux 并发模型

调用阻塞API

Reactor 和 RxJava 提供了 publishOn 操作符,可以在不同的线程上继续处理。

publishOn 它可以改变数据流中后续操作符的执行上下文,即将一个 Publisher 的执行切换到另一个 Scheduler 上。

Flux.range(1, 10)
// 并行调度器(由 reactor 提供)
.publishOn(Schedulers.parallel())
.map(i -> i * i)
.subscribe(System.out::println);

Reactor 提供了以下几种类型的调度器(Schedulers):

  1. Schedulers.immediate():立即在当前线程执行任务,如果当前线程不能立即执行任务,那么它会创建一个新的线程来执行任务。
  2. Schedulers.single():创建一个只有一个线程的调度器,所有任务都在这个单一线程中执行。这个调度器适合用于不需要并发执行的长时间运行的任务。
  3. Schedulers.elastic():创建一个弹性的线程池,线程池的大小会根据需要动态调整。这个调度器适合用于 I/O 阻塞的场景。
  4. Schedulers.parallel():创建一个支持并行执行的调度器,线程池的大小默认等于处理器的数量。这个调度器适合用于 CPU 密集型的工作。
  5. Schedulers.fromExecutor(Executor executor):创建一个使用自定义 Java Executor 的调度器。
  6. Schedulers.newSingle(String name):创建一个新的单线程调度器。
  7. Schedulers.newParallel(String name, int parallelism):创建一个新的并行调度器,可以指定并行度。
  8. Schedulers.newElastic(String name, int ttlSeconds):创建一个新的弹性调度器,可以指定线程的生存时间。

可变的状态

在 Reactor 和 RxJava 这样的反应式编程库中,我们通过一系列的操作符来定义数据流的处理逻辑。这些操作符会在运行时形成一个 reactive pipeline,数据会按照这个 pipeline 中的操作符的顺序,依次在各个阶段进行处理。(避免并发问题。)

Flux.range(1, 5)
.map(i -> i * i) // 第一个操作符,用于计算平方
.filter(i -> i % 2 == 0) // 第二个操作符,用于过滤偶数
.subscribe(System.out::println); // 订阅并打印结果

这个 reactive pipeline 在运行时,数据会按照 map -> filter -> subscribe 的顺序依次处理。每个操作符都不会被并发调用,所以我们不需要担心并发问题。例如,即使 map 操作符在处理不同的数据时,它也不会被并发调用,所以我们不需要担心它的内部状态(例如一个计数器)会被并发修改。

线程模型

在使用 Spring WebFlux 运行的服务器上,你应该看到哪些线程?

  • 如果你只使用 Spring WebFlux 的基础功能(没有使用如数据库访问等额外的功能),那么你的服务器大概会有一个线程用于服务器本身的运行,还有几个线程用于处理用户的请求,这几个线程的数量通常和你的 CPU 核心数一样多。但是,如果你的服务器是一个 Servlet 容器,比如 Tomcat,那么它可能会有更多的线程。这是因为 Servlet 容器需要同时支持传统的阻塞 I/O 和 Servlet 3.1 引入的非阻塞 I/O。阻塞 I/O 需要为每个请求分配一个线程,所以需要更多的线程。
  • 响应式的 WebClint 操作是以事件循环的方式进行的。因此,看到的与之相关的线程数量是小且固定的。
  • Reactor 和 RxJava 提供了线程池抽象,称为调度器(scheduler)
  • 数据访问库和其他第三方依赖也可以创建和使用它们自己的线程。

配置

Spring 并不提供 start 和 stop 服务器的支持,如果使用的是 Spring Boot ,那么可以直接配置 WebClient。

Reactive 核心

  • Server
    • HttpHandle
    • WebHandle API
  • Client
    • ClientHttpConnector
WebClient webClient = WebClient.create("http://localhost:8080");
Mono<String> result = webClient.get()
.uri("/hello")
.retrieve()
.bodyToMono(String.class);

HttpHandle

Netty

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

WebHandler API

package: org.springframework.web.server

通过多个 WebExceptionHandler、多个 WebFilter、单个WebHandler 链处理请求。(通过 WebHttpHandlerBuilder 组装)

  • WebExceptionHandler 处理请求过程中出现的异常(根据注册顺序来处理异常)
  • WebFilter 对请求进行预处理(根据注册顺序来处理请求)
  • WebHandler 处理请求,在一个请求处理链中,只能有一个 WebHandler

WebHandler 的作用

  • User session with attributes.
  • Request attributes.
  • Resolved Locale or principal for the request.
  • Access to parsed and cached form data.
  • Abstractions for multipart data.
  • and more...

Filter

在 Spring WebFlux中,WebFilter 是一个用于处理 HTTP 请求的接口,它可以在请求被 WebHandler 处理之前和之后执行一些逻辑。 这种逻辑可以包括修改请求或响应、添加日志、添加安全检查等。

CORS

内置了 CorsWebFilter.

Exceptions

使用 WebExceptionHandler(接口) 来处理 WebHandler 处理链的异常。

WebExceptionHandler 的实现类

  • ResponseStatusExceptionHandler 处理 ResponseStatusException 异常,会将响应的HTTP状态码设置为异常的状态码。
  • WebFluxResponseStatusExceptionHandler 是ResponseStatusExceptionHandler的扩展。除了处理ResponseStatusException,它还可以处理带有@ResponseStatus注解的异常。当抛出这种异常时,WebFluxResponseStatusExceptionHandler会将响应的HTTP状态码设置为注解的状态码。

Codecs

  • Encoder Decoder: 处理HTTP请求和响应的数据的关键组件。编解码器可以将数据从一种格式转换为另一种格式。例如,一个编解码器可以将JSON数据转换为Java对象,或者将Java对象转换为JSON数据。
  • HttpMessageReader HttpMessageWriter: 定义了如何读取和写入HTTP消息。你可以使用这些接口来处理特定类型的HTTP消息,例如表单数据、多部分内容、服务器发送的事件等。
  • EncoderHttpMessageWriter DecoderHttpMessageReader:
  • DataBuffer: 所有的编解码器都在DataBuffer上工作,DataBuffer是一个抽象的字节缓冲区表示,它可以表示多种类型的字节缓冲区,例如Netty的ByteBuf、Java的ByteBuffer等。

Jackson JSON

Jackson2Decoder

  • Jackson 的异步、非阻塞处理器将字节块流转换成 TokenBuffer,每一个 TokenBuffer 代表一个 JSON 对象。
  • 每一个 TokenBuffer 对象通过 ObjectMapper 转换成更高级的对象。
  • 解码 single-value 时,只有一个 TokenBuffer.
  • 解码 multi-value 时,有多个 TokenBuffer.

Jackson2Encoder

  • single-value 使用 ObjectMapper
  • multi-value(application/json) 使用 Flux#collectToList()
  • multi-value(application/x-ndjson or application/stream+x-jackson-simle)

Form Data

FormHttpMessageReader FormHttpMessageWriter 支持编码和解码 application/x-www-form-urlencoded 的内容

一旦你使用了 getFormData()方法,原始的请求体就不能再被读取了。这是因为getFormData() 方法会将请求体中的数据读取到一个缓存中,然后返回这个缓存。一旦数据被读取到缓存中,原始的请求体就会被清空。

Multipart

MultipartHttpMessageReader MultipartHttpMessageWriter 支持编码和解码 multipart/form-data multipart/mixed multipart/related 的内容.

一旦你使用了 getMultipartData()方法,原始的请求体就不能再被读取了。这是因为 getMultipartData() 方法会将请求体中的数据读取到一个缓存中,然后返回这个缓存。一旦数据被读取到缓存中,原始的请求体就会被清空。

Limits

Streaming

DataBuffer

日志

Log id

org.springframework.web.server.ServerWebExchange.getLogPrefix

Sensitive Data

@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {

@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().enableLoggingRequestDetails(true);
}
}
Consumer<ClientCodecConfigurer> consumer = configurer ->
configurer.defaultCodecs().enableLoggingRequestDetails(true);

WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies -> strategies.codecs(consumer))
.build();

Appenders

Custom codecs

WebClient webClient = WebClient.builder()
.codecs(configurer -> {
CustomDecoder decoder = new CustomDecoder();
configurer.customCodecs().registerWithDefaultConfig(decoder);
})
.build();

DispatcherHandler

front controller (前端控制器模式)

central WebHandler: DispatchHandler, 为请求处理 提供一个共享算法。

WebHttpHandlerBuilder

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

Special Bean Types

实现了 WebFlux 框架协议的 Spring 管理的对象实例。

  • HandleMapping 将请求映射到处理器,返回 Mono 或 Flux 类型
  • HandlerAdapter 适配不同类型的处理器
  • HandlerResultHandler 处理控制器方法的返回值。在响应式编程模型中,它处理的通常是Mono或Flux类型的返回值,将这些返回值转换为发送给客户端的响应。

WebFlux Config

Processing

  • Each HandlerMapping is asked to find a matching handler, and the first match is used.
  • If a handler is found, it is run through an appropriate HandlerAdapter, which exposes the return value from the execution as HandlerResult.
  • The HandlerResult is given to an appropriate HandlerResultHandler to complete processing by writing to the response directly or by using a view to render.

Result Handling

HandlerAdapter -> HandleResult -> HandlerResultHandler.

  • ResponseEntityResultHandler
  • ServerResponseResultHandler
  • ResponseBodyResultHandler
  • ViewResolutionResultHandler

Exceptions

HandlerAdapter 的实现版本能够在内部处理在调用请求处理器(如控制器方法)时产生的异常。

View Resolution

HandlerResultHandler

Handling

传递给 ViewResolutionResultHandler 的 HandlerResult 包含 handler 返回值和在请求处理期间添加了属性的 model 。

返回值是下面的其中一个

  • String,CharSequence : 逻辑视图的名称,通过一系列配置的 ViewResolver 解析成一个 View。
  • void:基于请求路径(URL)选择一个默认视图名称。
  • Rendering:API for view resolution scenarios
  • Model,Map:额外的 model attributes 性将被添加到该请求的 model 中。
  • ohters

Redirecting

在视图名称前加入 redirect: 前缀。

UrlBasedViewResolver

Rendering.redirectTo("abc").build()

Content Negotiation

ViewResolutionResultHandler

HttpMessageWriterView

Annotation Controller

Spring WebFlux 提供了一个基于注解的编程模型

@RestController
public class HelloController {

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

@Controller

为了确保 @Controller 被正确使用,需要这个 Bean 能被扫描

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

@RestControler = @Controller + @ResponseBody

AOP Proxies

for example: @Transactional

@EnableTransactionManagement

Mapping Requests

@RequestMapping

可以匹配 URL, HTTP method, request parameters, headers, and media types.

variants

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

URI Patterns

  • ? 匹配一个字符(0..1)
  • * 在一个 Ptah segment 中匹配任意字符(0..n) , "/resources/*.png" 可匹配 "/resources/file.png" 但是不匹配 "/resources/folder/file.png"
  • ** 匹配一个或多个 path segment 直到 path 结束。 "/resources/**" 可匹配 "/resources/file.png" 也可以匹配 "/resources/folder/file.png"
  • {name} 匹配一个 path segment 并且捕获它,作为一个 name 的变量。 "/projects/{project}/versions" 可匹配 "/projects/spring-web/versions" 并且捕获 "spring-web" 作为 project 变量的值。
  • {name:[a-z]+} 匹配正则 a-z 作为 path variable 的值。"/projects/{project:[a-z]+}/versions" 可匹配 "/projects/spring/versions" 但不匹配 "/projects/spring1/versions"
  • {*path} 匹配零个或多个 path segment 直到 path 结束,并将其捕获为 path 变量的值。"/resources/{*file}" 匹配 "/resources/images/file.png" and captures file=/images/file.png

URI 变量会被自动转成合适类型,或者抛出 TypeMismatchException 异常。

@Controller
@RequestMapping("/owners/{ownerId}") // Class-level URI
public class OwnerController {

@GetMapping("/pets/{petId}") // Method-level URI
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
// spring-web-3.0.5.jar
// version 3.0.5
// ext .jar
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}

Pattern Comparison

当多个模式与 URL 匹配时,必须对它们进行比较以找到最佳匹配。

PathPattern.SPECIFICITY_COMPARATOR

Consumable Media Types

指定 Content-Type

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

Producible Media Types

指定 Accept

@GetMapping(path = "/pets/{petId}", produces = "application/json")
// @GetMapping(path = "/pets/{petId}", produces = "!application/json") 表示非 application/json
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}

Parameters and Headers

根据查询参数条件缩小映射范围

  • 参数是否存在 myParam !myParam
  • 参数是否为特定值 myParam=myValue

参数

// 判断 myParan 是否等于 myValue
// 处理 GET /pets/123?myParam=myValue
// 不处理 GET /pets/123?myParam=otherValue
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}

请求头

@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}

HTTP HEAD,OPTIONS

Custom Annotations

实现 RequestMappingHandlerMapping,重写 getCustomMethodCondition ,返回 RequestCondition

Explicit Registrations

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

@HttpExchange

可以使用 @HttpExchange 替换 @RequestMapping

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

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

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

Handler Methods

Method Arguments

  • ServerWebExchange:可以访问 HTTP 请求、响应,以及请求和 session 中的值,checkNotModified methods, and others.
  • ServerHttpRequest、ServerHttpResponse
  • WebSession:访问 session
  • java.security.Principal:通常用于表示进行身份验证的实体。
  • org.springframework.http.HttpMethod:http 请求方法
  • java.util.Locale:请求区域设置
  • java.util.TimeZone + java.time.ZoneId:时区
  • @PathVariable:URI 模版变量。
  • @MatrixVariable:name-value pairs in URI path segments
  • @RequestParam:请求参数
  • @RequestHeader:请求头
  • @CookieValue:cookie
  • @RequestBody
  • HttpEntity<B> : 请求头和请求体
  • @RequestPart : multipart/form-data 请求的数据
  • java.util.Map、org.springframework.ui.Model、org.springframework.ui.ModelMap:用于访问 HTML 控制器中使用的模型,并作为视图渲染的一部分公开给模板。
  • @ModelAttribute:访问模型中现有属性。
  • Errors、BindingResult
  • SessionStatus + class-level @SessionAttributes
  • UriComponentsBuilder
  • @SessionAttribute:访问任何会话属性
  • @RequestAttribute:访问请求属性
  • Any other argument

Return Values

  • @ResponseBody
  • HttpEntity<B>, ResponseEntity<B>
  • HttpHeaders
  • ErrorResponse
  • ProblemDetail
  • String
  • View
  • java.util.Map, org.springframework.ui.Model
  • @ModelAttribute
  • Rendering
  • void
  • Flux<ServerSentEvent>, Observable<ServerSentEvent>, or other reactive type
  • Other return values

Type Conversion

Matrix Variables

Matrix Variables 是一种特殊类型的 URL 参数,它们允许在特定的 URL 路径段中包含键值对。这些变量在 URL 路径中以分号(;)开始,然后是键值对,键和值之间用等号(=)分隔。Matrix Variables 可以出现在 URI 的任何 path segment 中。

http://example.com/cars;color=red;year=2012 color 和 year 就是 Matrix Variables,它们的值分别是 red 和 2012。

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

// 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
}

// 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

  • 可以使用 @RequestParam 注解,在 controller 中将 HTTP 请求参数转为方法(method)的参数。
  • 可以使用 required 来指定方法参数是否为必传。
  • @RequestParam 可以声明在一个 Map<String, String> or MultiValueMap<String, String> 上。这样,所有的请求参数都会被放到这个 map 中。
@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";
}
}

@RequestHeader

  • 可以使用 @RequestHeader 注解绑定一个请求头到方法的参数中。
  • @RequestHeader 可以声明在一个 Map<String, String> or MultiValueMap<String, String> 上。这样,所有的请求头都会被放到这个 map 中。
  • 逗号分割的字符串可以转成 String、String[]、List<String>
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}

@CookieValue

  • 可以使用 @CookieValue 注解绑定一个 cookie 到方法的参数中。
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}

@ModelAttribute

  • 可以使用 @ModelAttribute 注解绑定一个 model attribute 到方法的参数中。
  • 默认情况下,构造函数和属性都会进行数据绑定,但是为了安全考虑,建议转为 web 定制对象,或者只使用构造函数来进行数据绑定。
  • 通过 @BindParam 在构造函数中指定请求参数的名称
  • 数据绑定失败会报错 WebExchangeBindException 异常,但是可以通过 BindingResult 来处理错误。注意:要使用 BindingResult 参数,就必须在前面声明一个 @ModelAttribute 参数,并且不能使用 reactive type wrapper (Mono、Flux 等)。
  • 使用 @Valid(jakarta.validation.Valid) 或者 @Validated(Spring) 在参数绑定之后进行参数校验。如果使用了 @Constraint 注解,在校验失败时会抛出 HandlerMethodValidationException 异常,而不是 MethodArgumentNotValidException 异常。
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
// ...
}

class Account {
private final String firstName;

// 指定请求参数的名称
public Account(@BindParam("first-name") String firstName) {
this.firstName = firstName;
}
}

// 如果 pet 对象绑定失败,并不会抛出 WebExchangeBindException 异常,异常会被记录在 BindingResult 对象中。
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}

// reactive type wrapper 风格的异常处理
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
return petMono
.flatMap(pet -> {
// ...
})
.onErrorResume(ex -> {
// ...
});
}

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

@SessionAttributes

  • @SessionAttributes 用于存储 model attributeWebSession 的请求中。
  • @SessionAttributes("pet") 表示参数名为 pet 的值会存到 session 中。
  • @SessionAttributes(value = {"employee", "pet"}, types = {Employee.class, Pet.class} 指定存储多个属性,并指明其类型。
  • 类级别注解
  • 在同一个 session 内共享这些对象
  • 第一次请求时,当一个名为 pet 的模型属性被添加到模型中,它会自动被提升并保存在 WebSession 中。 它会一直保留在那里,直到另一个控制器方法使用 SessionStatus 方法参数来清除存储。
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}

@Controller
@SessionAttributes("pet")
public class EditPetForm {
@PostMapping("/pets/{id}")
// 这个 pet 对象会存到 session 中。
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors()) {
// ...
}
// 清空当前 session 中的 sessionAttributes 数据。
status.setComplete();
// ...
}
}

@PostMapping("/pets/{id}")
// 通过 @ModelAttribute("pet") Pet pet 获取到 session 中的 pet 对象。
// @ModelAttribute("pet") 不允许省略。
public String handle(@ModelAttribute("pet") Pet pet, BindingResult errors, SessionStatus status) {
// ... use the pet object
}
}

@SessionAttribute

  • 访问 session attribute
  • 要添加或者删除 session attributes,consider injecting WebSession into the controller method.
@GetMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}

RequestAttribute

Multipart Content

ServerWebExchange 提供了一些方法来访问 multipart 数据。

class MyForm {

private String name;

private MultipartFile file;

// ...

}

@Controller
public class FileUploadController {

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

}

PartEvent

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

@RequestBody

HttpEntity

@ResponseBody

ResponseEntity

Jackson JSON

@JsonView 的使用: https://segmentfault.com/a/1190000023286635

@JsonView 用于控制,后台返回给前台时显示的 json 字段(即,某些字段不返回)。