controller参数接收
# Controller参数接收
# Controller层的接口
在 SpringBoot 的 @Controller
层中,GET 请求和 POST 请求的参数接收方式有所不同,主要区别在于参数的位置和传递方式。
# Get请求的参数
GET 请求的参数通常通过 URL 的查询参数(Query Parameters) 或 路径变量(Path Variables) 传递。
其中 @RequestParam 和 @PathVariable 最为常用
# @RequestParam
- 作用:从 URL 的查询参数中获取单个或多个参数。
- 适用场景:获取 URL 中
?key=value
格式的参数。
@GetMapping("/query")
public String getParam(
@RequestParam String name, // 必填参数
@RequestParam(required = false) Integer age, // 可选参数
@RequestParam(defaultValue = "0") int score // 默认值
) {
return "Name: " + name + ", Age: " + age + ", Score: " + score;
}
2
3
4
5
6
7
8
# @PathVariable
- 作用:从 URL 的路径变量中获取参数。
- 适用场景:RESTful 风格的 URL 路径参数(如
/user/{id}
)。
@GetMapping("/user/{id}")
public String pathVariable(
@PathVariable Long id, // 路径变量
@PathVariable(name = "id") String userId // 显式指定变量名
) {
return "User ID: " + id + ", User String ID: " + userId;
}
2
3
4
5
6
7
# 直接使用方法参数
- 作用:Spring 会自动将 URL 中的查询参数绑定到方法参数。
@GetMapping("/auto-bind")
public String autoBind(String name, Integer age) {
return "Name: " + name + ", Age: " + age;
}
2
3
4
- 请求示例:
/auto-bind?name=Alice&age=25
输出:Name: Alice, Age: 25
# ttpServletRequest
- 作用:通过
request.getParameter()
获取参数。
@GetMapping("/request")
public String viaRequest(HttpServletRequest request) {
String name = request.getParameter("name");
return "Name: " + name;
}
2
3
4
5
# Map
接收所有参数
- 作用:将所有查询参数存入
Map
对象。
@GetMapping("/params-map")
public String viaMap(@RequestParam Map<String, Object> params) {
return "Name: " + params.get("name") + ", Age: " + params.get("age");
}
2
3
4
# Object 对象来接收参数
当然在实际开发中,还有一种常用的数据接收方式,方法参数中为 自定义的实体类。比如:
@RestController
public class OrderController {
@GetMapping("/orderInfoList")
public Result<?> InfoList(FOrderInfo vo) {
return Result.success(vo);
}
}
2
3
4
5
6
7
8
在 Spring Boot 中,如果你在 @GetMapping
的 Controller 方法中使用一个自定义对象(如 FOrderInfo vo
)作为参数,Spring 会自动将请求中的参数按照 字段名称匹配 的方式映射到这个对象的属性上。
public class FOrderInfo {
private String orderId;
private Integer status;
// getters and setters
}
2
3
4
5
6
如果请求如下:
GET /orderInfoList?orderId=12345&status=1
Spring Boot 会自动创建一个 FOrderInfo
的实例,并将:
orderId
映射为"12345"
status
映射为1
注意:这种方式,字段名称必须匹配才行。
- URL 参数名要与实体类的 getter/setter 对应的属性名一致。
- 比如:想要映射
userName
字段,URL 参数也应该是userName=xxx
。
使用该方式,还可能存在类型转换问题:
- Spring 会尝试自动进行基础类型的转换(如 String -> Integer 等),如果失败可能会抛出异常或绑定失败(可通过
@Valid
或@RequestParam
做额外校验)。
# Post请求的参数
POST 请求的参数通常通过 请求体(Request Body) 或 表单数据(Form Data) 传递。
post类型的请求存在数据的地方可比Get多,所以内容的形式也比Get多,主要有以下几种:
- url
- Body
# @RequestParam
- 作用:接收表单数据(
Content-Type: application/x-www-form-urlencoded
)。 - 适用场景:表单提交或简单键值对数据。
@PostMapping("/post-form")
public String postForm(
@RequestParam String username,
@RequestParam("pwd") String password
) {
return "Username: " + username + ", Password: " + password;
}
2
3
4
5
6
7
表单提交实例
<form action="/post-form" method="post">
<input type="text" name="username" />
<input type="password" name="pwd" />
<input type="submit" />
</form>
2
3
4
5
# @RequestBody
- 作用:接收 JSON 格式的请求体(
Content-Type: application/json
)。(也只有它能处理json请求) - 适用场景:复杂数据结构(如对象、数组)。
@PostMapping("/post-json")
public String postJson(@RequestBody User user) {
return "Name: " + user.getName() + ", Age: " + user.getAge();
}
2
3
4
请求体
{
"name": "Bob",
"age": 30
}
2
3
4
# 实体类直接接收
- 作用:将表单数据绑定到实体类。
@PostMapping("/post-entity")
public String postEntity(User user) {
return "Name: " + user.getName() + ", Age: " + user.getAge();
}
@PostMapping("/post-entity")
public String postEntity(ModelAttribute User user) {
return "Name: " + user.getName() + ", Age: " + user.getAge();
}
2
3
4
5
6
7
8
9
10
表单提交时,字段名需与实体类属性一致。
如果发现无法绑定,则加上 @ModelAttribute
# Map
接收请求体
- 作用:将 JSON 或表单数据存入
Map
对象。
@PostMapping("/post-map")
public String postMap(@RequestBody Map<String, Object> params) {
return "Name: " + params.get("name") + ", Age: " + params.get("age");
}
2
3
4
# 文件上传 @RequestParam + MultipartFile
- 作用:接收文件上传的参数。
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
return "File name: " + file.getOriginalFilename();
}
2
3
4
表单需设置 enctype="multipart/form-data"
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
2
3
4
# 文件上传的获取【扩展】
当表单中包含文件字段时,必须设置表单的 enctype="multipart/form-data"
。
这种格式的请求体被分割为多个 part(部分),每个 part 可以是表单字段或文件内容。
请求示例
-----------------------------3141592653589793238
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
(文件内容)
-----------------------------3141592653589793238
Content-Disposition: form-data; name="description"
这是一个测试文件
-----------------------------3141592653589793238--
2
3
4
5
6
7
8
9
10
每个 part
通过边界符(boundary)分隔,包含元数据(如文件名、类型)和实际内容。
# @RequestParam
的设计目的
@RequestParam
的核心作用:- 用于绑定 表单字段 或 文件 到方法参数。
- 它可以处理 简单键值对(如
application/x-www-form-urlencoded
)或 multipart 请求中的文件/字段。 - 对于文件上传,
@RequestParam
会绑定到MultipartFile
对象,该对象封装了文件的元数据和内容。
- 为什么不用
@RequestBody
?@RequestBody
期望请求体是一个 完整的对象(如 JSON 或 XML),并需要将其反序列化为 Java 对象。multipart/form-data
的结构不符合@RequestBody
的预期:- 请求体是多个独立的 part,而非一个整体对象。
- Spring 没有内置的机制将
multipart/form-data
反序列化为对象,因此无法使用@RequestBody
。
# Spring 如何处理文件上传
(1) 核心组件:MultipartResolver
- 作用:
- Spring 通过
MultipartResolver
解析multipart/form-data
请求。 - 将请求体中的各个 part 解析为
MultipartFile
对象,并封装到MultipartHttpServletRequest
中。
- Spring 通过
- 默认实现:
- Spring Boot 内置了
StandardServletMultipartResolver
,基于 Servlet 3.0 的 API 实现。
- Spring Boot 内置了
(2) 方法参数绑定
- 通过
@RequestParam
绑定文件:
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
// 处理文件
}
2
3
4
@RequestParam("file")
指定绑定到表单字段名为 file
的 part。
Spring 自动将对应的 MultipartFile
对象注入到方法参数中。
- 根本原因:
@RequestBody
期望请求体是一个 结构化的数据对象(如 JSON),而multipart/form-data
是一种 分片数据格式,无法直接映射为对象。
# 多文件上传处理
通过 MultipartFile[] files 获取文件
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@RestController
public class FileUploadController {
// 从配置文件中读取文件保存路径
@Value("${file.upload-dir}")
private String uploadDir;
@PostMapping("/upload-multiple")
public String handleMultipleFiles(
@RequestParam("files") MultipartFile[] files) { // 接收文件数组
if (files == null || files.length == 0) {
return "上传失败:没有选择文件!";
}
StringBuilder successMessage = new StringBuilder();
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue; // 跳过空文件
}
try {
// 生成唯一文件名(避免重复)
String originalFilename = file.getOriginalFilename();
String fileName = System.currentTimeMillis() + "_" + originalFilename;
// 保存路径
Path uploadPath = Path.of(uploadDir).toAbsolutePath().normalize();
Path targetLocation = uploadPath.resolve(fileName);
// 创建目录(如果不存在)
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 保存文件
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
successMessage.append("文件 ").append(fileName).append(" 上传成功!\n");
} catch (IOException e) {
return "上传失败:" + e.getMessage();
}
}
return "所有文件上传成功!\n" + successMessage.toString();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
在处理文件上传时,请记得在 application.properties
中配置文件上传目录和大小限制:
# 文件保存路径(绝对路径或相对路径)
file.upload-dir=uploads
# 配置文件上传大小限制(例如:每个文件最大10MB,总请求最大50MB)
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=50MB
2
3
4
5
6
注意:关于,@RequestParam("files") MultipartFile[] files
,参数,有下面2点要注意
name="files"
必须与前端表单的input
标签的name
属性一致。- Spring 会自动将多个文件封装为
MultipartFile
数组。
# 几个问题
# Get的Body为什么不能放参数?
根据 HTTP 协议的官方定义(RFC 7231):
- GET 请求的语义:GET 方法的目的是获取资源,而不是修改或提交数据。协议明确规定,GET 请求的参数应通过 URL 的查询字符串(Query String) 传递,而请求体(Body)通常被忽略。
- 服务器的处理方式:大多数服务器(如 Apache、Nginx、Tomcat)在接收到 GET 请求时,会忽略请求体中的内容,即使客户端发送了 Body。因此,依赖 Body 传递参数可能导致数据丢失。(规范虽然未明确禁止GET携带Body,但指出Body对GET请求没有语义意义,因此服务器常常选择忽略它。)
技术限制:
- URL 的长度限制:
- GET 的参数通过 URL 传递,而 URL 的长度在浏览器和服务器中是有限制的(通常 2KB 到 8KB)。如果参数过多或过大,可能导致 URL 超长,引发错误。
- Body 没有理论上的长度限制(取决于服务器配置),但 GET 的参数不适合放在 Body 中,因为协议设计不支持(Get的目的是为了获取资源)。
- 客户端和服务器的兼容性:
- 大部分客户端(如浏览器、HTTP 客户端库)在发送 GET 请求时,默认不会发送 Body,即使尝试发送也可能被拦截或忽略。
- 服务器端框架(如 Spring、Express)通常会直接忽略 GET 请求的 Body,或抛出异常(如某些框架明确禁止 GET 的 Body)。
# Post的url可以携带参数吗?
可以的。
POST 请求的 URL 可以携带参数,这些参数通常以 查询参数(Query Parameters) 的形式附加在 URL 的末尾(例如 ?key=value
)。同时,POST 请求的参数也可以放在 请求体(Request Body) 中,比如:
POST /api/data?sort=desc&page=2 HTTP/1.1
Content-Type: application/json
2
- 获取 URL 中的查询参数
使用 @RequestParam
注解或直接通过方法参数绑定。
@PostMapping("/api/data")
public String handlePostRequest(
@RequestParam String sort, // 必填参数
@RequestParam(required = false) Integer page, // 可选参数
@RequestParam(defaultValue = "0") int limit // 默认值
) {
return "Sort: " + sort + ", Page: " + page + ", Limit: " + limit;
}
2
3
4
5
6
7
8
请求示例
POST /api/data?sort=desc&page=2&limit=10 HTTP/1.1
Content-Type: application/json
2
- 获取请求体(Body)中的参数
使用 @RequestBody
注解接收 JSON 数据,或通过表单数据绑定。
@PostMapping("/api/data")
public String handlePostRequest(
@RequestBody User user // 接收 JSON 数据
) {
return "Name: " + user.getName() + ", Age: " + user.getAge();
}
2
3
4
5
6
请求示例
POST /api/data HTTP/1.1
Content-Type: application/json
{
"name": "Alice",
"age": 30
}
2
3
4
5
6
7
- 同时获取 URL 参数和请求体参数
@PostMapping("/api/data")
public String handlePostRequest(
@RequestParam String sort, // URL 参数
@RequestBody User user // 请求体参数
) {
return "Sort: " + sort + ", User Name: " + user.getName();
}
2
3
4
5
6
7
请求示例
POST /api/data?sort=asc HTTP/1.1
Content-Type: application/json
{
"name": "Bob",
"age": 25
}
2
3
4
5
6
7
# 疑问【@RequestParam】
简单表单提交不是application/x-www-form-urlencoded吗,它的参数是存放在Body中的呀,不应该是使用@RequestBody获取吗?前文Post关于@RequestParam的参数也说,它是用于获取简单表单提交的参数。
怎么回事呢?
表单数据(x-www-form-urlencoded
)的处理方式
- 表单数据的 Content-Type:
- 表单提交的默认
Content-Type
是application/x-www-form-urlencoded
,数据格式为键值对(如username=admin&password=123
),存储在请求体(Body)中。 - Spring 对这类数据的处理是自动的,即使不使用
@RequestBody
,而通过@RequestParam
或直接方法参数绑定也能获取
- 表单提交的默认
# @RequestParam的局限
使用@RequestParam,也是可以获取请求体中的参数的,但仅限于表单形式
所以总结下,如果请求内容形式为:
application/x-www-form-urlencoded
时,
@RequestBody
和@RequestParam
可以都可以获取对应参数如果,请求中url中有参数,且Body中也有参数,并且参数名称都能对上,那**
@RequestParam
**2个地方的值都会获取到。【注意】
如果请求body中的content-Type形式为 Application/json,那么无法使用**
@RequestParam
**只能使用
@RequestBody
了。
@RequestBody
的作用:
- 用于接收 非表单格式 的请求体数据(如 JSON、XML),需要明确指定
Content-Type
(如application/json
)。 - 表单数据(
x-www-form-urlencoded
)的格式与 JSON 不同,Spring 默认会将其解析为键值对,而非对象。
# Spring中对流数据的处理
使用@RequestBody接收流数据可能会有问题,因为默认情况下它会一次性读取整个请求体,导致内存溢出,特别是处理大文件或持续数据流的时候。如果说用户需要处理持续的数据流,比如视频流,这时候又该怎么办呢?该采取什么办法呢。
以上问题,市面上都有一套成熟的解决方案了,在处理流数据(如视频流、实时数据流等)时,你可以需要根据具体场景选择合适的技术方案。
以下是针对不同场景的实现方法和示例代码:
# 收上传的视频流(通过 HTTP 的 multipart/form-data
)
适用于客户端上传视频文件的场景,使用 MultipartFile
处理。
对应后端代码示例:
@PostMapping("/upload-video")
public String handleVideoUpload(@RequestParam("video") MultipartFile video) {
if (video.isEmpty()) {
return "上传失败:视频为空!";
}
try {
// 保存视频到指定路径
Path targetPath = Paths.get("uploads/" + video.getOriginalFilename());
video.transferTo(targetPath);
return "视频上传成功!";
} catch (IOException e) {
return "上传失败:" + e.getMessage();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这里的就是上文中提到的 @RequestParam和MultipartFile 。
# 接收实时流数据(如视频流、传感器数据)
适用于需要持续接收数据流的场景,如实时监控、日志流等。推荐使用 Spring WebFlux 的响应式编程模型。
# 实现步骤
- 使用
Flux
接收流式数据:- 通过
ServerSentEvent
(SSE)或WebSocket
实现流式传输。 - 示例:接收客户端发送的分块数据流。
- 通过
@RestController
public class StreamController {
@PostMapping("/stream")
public Mono<String> handleStreamData(ServerWebExchange exchange) {
Flux<DataBuffer> flux = exchange.getRequest().getBody();
return flux
.map(buffer -> {
// 逐块处理数据(如视频流的分片)
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
// 处理逻辑(如解码、存储)
return new String(bytes);
})
.doOnNext(data -> System.out.println("Received chunk: " + data))
.then(Mono.just("数据流处理完成!"));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用分块传输编码(Chunked Transfer Encoding)
通过 HTTP 头 Transfer-Encoding: chunked
实现服务器推送流数据。
# 实现步骤
- 服务端推送流数据:
@GetMapping("/stream-data")
public Flux<String> streamData() {
return Flux.interval(Duration.ofSeconds(1))
.map(i -> "实时数据块 " + i)
.doOnNext(data -> System.out.println("发送数据:" + data));
}
2
3
4
5
6
- 客户端接收(如使用
WebClient
):
WebClient.create("http://localhost:8080")
.get()
.uri("/stream-data")
.retrieve()
.bodyToFlux(String.class)
.subscribe(data -> System.out.println("接收到:" + data));
2
3
4
5
6
# Chunked Transfer Encoding的作用
在 HTTP 协议中,Transfer-Encoding: chunked
是一种用于分块传输数据的编码方式,它允许服务器或客户端将数据划分为多个“块”(chunks)逐步传输,而无需预先知道完整数据的长度。以下是其核心含义、工作原理和典型应用场景的详细说明:
- 动态生成内容
当服务器无法提前确定响应内容的完整长度(例如实时生成的流数据、动态计算结果或大文件分块传输)时,使用
chunked
编码可以边生成边传输,避免等待所有数据就绪。 - 流式传输支持 适用于需要实时推送数据的场景(如视频流、日志实时输出、聊天消息等),客户端可以逐步接收并处理数据,降低延迟。
- 替代
Content-Length
头 当无法预先计算Content-Length
(如数据长度未知或计算成本高)时,chunked
编码允许省略该头部,直接分块传输。
传输流程
- 服务器按需生成数据块,逐个发送给客户端。
- 每个块以十六进制长度值开头,后跟
\r\n
,然后是实际数据。 - 最后一个块为长度
0
的空块,表示传输结束。
# 使用场景
1.服务器推送流数据
- 视频/音频流:服务端持续发送媒体数据块,客户端逐步播放。
- 实时日志:动态生成的日志内容通过分块传输实时展示在客户端。
- Server-Sent Events (SSE):长连接中服务器持续推送事件数据。
- 大文件上传/下载
- 客户端上传大文件时,可将文件分块发送(请求头中设置
Transfer-Encoding: chunked
)。 - 服务端下载大文件时,分块返回数据(响应头中设置
Transfer-Encoding: chunked
)。
- 动态内容生成
- 如实时计算、数据库查询结果流式返回,无需等待所有结果就绪。
特性 | Transfer-Encoding: chunked | Content-Length |
---|---|---|
数据长度 | 动态分块传输,无需提前知道总长度 | 需预先计算完整数据长度 |
适用场景 | 流数据、动态生成内容、大文件传输 | 静态资源、已知长度的数据 |
内存占用 | 低(逐块处理) | 高(需缓存完整数据) |
协议版本 | HTTP/1.1 及以上支持 | HTTP/1.0 和 1.1 均支持 |
# 跟数据流之类的关系
常见的视频流传输确实会利用类似 分块传输 的逻辑,但具体实现方式可能与 Transfer-Encoding: chunked
略有不同。
视频流传输通常依赖以下两种主流技术: 基于 HTTP 的自适应流(HLS、DASH)
当然还有其他技术,欢迎大家自行查阅
传输特点:
- 视频被预先分片,存储在服务器或 CDN 中。
- 客户端通过 HTTP 短连接 按需请求分片文件(如
video_1.ts
,video_2.ts
)。 - 与
Transfer-Encoding: chunked
的区别:- 分片是独立的文件,而非动态分块传输。
- 每个分片有明确的
Content-Length
,不依赖chunked
编码。
# Spring WebFlux 的响应式编程模型
Spring MVC(传统Web框架)和Spring WebFlux 都是Spring Boot中的Web编程框架,但它们的设计理念、底层架构和适用场景有显著区别。
简单一句话,它也是一种web框架,但它是非阻塞的。
Spring WebFlux 是 Spring 5 引入的 非阻塞、响应式 Web 框架,基于 Reactor(实现了 Reactive Streams 规范)构建。其核心特点包括:
- 非阻塞 I/O:通过异步事件驱动,避免线程阻塞,提升高并发场景下的性能。
- 背压支持:消费者可以控制生产者的数据流速度,防止内存溢出。
- 函数式编程:支持通过
RouterFunction
和HandlerFunction
定义路由和处理器。
在 Spring WebFlux 中,响应式编程通过以下核心组件实现:
Flux<T>
:表示 0到N个元素 的异步数据流,适用于批量数据。Mono<T>
:表示 0或1个元素 的异步数据流,适用于单一结果。
SSE(Server-Sent Events) 是一种 单向实时通信协议,允许服务器主动向客户端推送数据流。其特点包括:
- 单向通信:数据仅从服务器流向客户端。
- 基于 HTTP:使用标准 HTTP 连接,无需额外协议。
- 自动重连:客户端断开后会自动重连(可配置重试间隔)。
- 事件流格式:数据以
text/event-stream
格式传输,每条消息以data:
开头。
以下是一个通过 Spring WebFlux 实现 SSE 的完整示例:
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
2
3
4
使用 Flux
和 ServerSentEvent
发送实时数据流:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;
@RestController
public class SSEController {
// 使用 Sinks 发布实时数据(模拟传感器数据)
private final Sinks.Many<Integer> sensorDataSink = Sinks.many().multicast().onBackpressureBuffer();
@GetMapping("/sse")
public Flux<ServerSentEvent<Integer>> streamSensorData() {
return sensorDataSink.asFlux()
.map(data -> ServerSentEvent.<Integer>builder()
.id(String.valueOf(System.currentTimeMillis()))
.event("sensor-data")
.data(data)
.build())
.onBackpressureDrop(); // 处理背压(丢弃旧数据)
}
// 模拟传感器数据生成(每秒发送一个随机数)
public void generateData() {
Flux.interval(Duration.ofSeconds(1))
.map(i -> (int) (Math.random() * 100))
.subscribe(sensorDataSink::tryEmitNext);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
客户端,使用JavaScript实现
使用 EventSource
连接 SSE 端点:
const eventSource = new EventSource("http://localhost:8080/sse");
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log("Received data:", data);
};
eventSource.onerror = function(error) {
console.error("SSE 连接错误:", error);
};
eventSource.addEventListener("open", function() {
console.log("连接已建立");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
# 以下是它跟传统Web框架的区别:
特性 | Spring MVC | Spring WebFlux |
---|---|---|
基础模型 | 阻塞式、同步模型(基于Servlet规范) | 非阻塞、异步、响应式模型(基于Reactive Streams) |
线程模型 | 每个请求分配一个线程,依赖线程池 | 事件驱动,少量线程处理所有请求(如Netty) |
返回类型 | 同步对象(如String 、ResponseEntity ) | 响应式类型(Mono 或Flux ) |
I/O操作 | 阻塞式I/O(如传统数据库操作) | 非阻塞式I/O(需配合响应式数据源,如R2DBC) |
核心设计目标
- Spring MVC:
- 适用场景:传统Web应用、轻量级异步需求、与阻塞式组件(如JDBC、传统数据库)集成。
- 优势:简单易用,生态成熟,对开发者的学习曲线低。
- 局限:高并发下因线程阻塞可能导致性能瓶颈。
- Spring WebFlux:
- 适用场景:高并发、低延迟场景(如实时监控、流式处理、WebSocket)、微服务架构。
- 优势:非阻塞模型支持更高吞吐量,响应式流处理(如SSE、大文件上传)。
- 局限:需要熟悉响应式编程,部分传统组件(如JDBC)需适配响应式接口。
注意:
它们两个不能共存:
Spring Boot项目中不能同时使用Spring MVC和WebFlux
,因为它们的核心架构和依赖冲突。
- 如果引入
spring-boot-starter-web
,默认启用Spring MVC。 - 如果引入
spring-boot-starter-webflux
,则启用WebFlux。
# 核心组件对比
组件 | Spring MVC | Spring WebFlux |
---|---|---|
前端控制器 | DispatcherServlet (Servlet规范) | DispatcherHandler (非Servlet) |
请求/响应对象 | ServletRequest /ServletResponse | ServerWebExchange (包含请求/响应) |
处理器 | @Controller (基于类) | @Controller 或函数式HandlerFunction |
过滤器 | Filter (Servlet过滤器) | WebFilter (响应式过滤器) |
异常处理 | @ControllerAdvice + @ExceptionHandler | 统一异常处理链(ErrorWebExceptionHandler ) |