0%

参数解析

Spring MVC 参数解析详解:按请求格式分类的原理与实践

Spring MVC 的参数解析机制是连接 HTTP 请求与 Controller 方法参数的核心桥梁,不同的请求格式(如表单、JSON、URL 路径参数、文件)对应不同的解析逻辑、注解和参数处理器。从 “格式特点→接收方式→参数处理器→底层原理→实战示例” 五个维度,系统解析 Spring MVC 如何将请求数据绑定到方法参数。

application/x-www-form-urlencoded:传统表单 / URL 编码格式

这是最经典的请求格式,适用于表单提交URL 查询字符串(如 ?name=张三&age=25),数据以键值对形式传输,且会进行 URL 编码(如空格转为 %20)。

1. 格式特点

  • 数据位置:URL 查询字符串(?key=value)或请求体(表单提交时);
  • 数据结构:扁平键值对(不支持嵌套对象,如 user.name 需拆分为 user.name=张三);
  • 编码方式:URL 编码(ASCII 字符外的内容转为 %XX 格式)。

2. 接收方式与参数处理器

根据参数类型(单个参数 / 对象),Spring MVC 会使用不同的参数处理器:

接收场景 注解 / 方式 核心参数处理器 适用场景
单个 / 多个简单参数 @RequestParam RequestParamMapMethodArgumentResolver 接收零散的键值对(如 nameage
复杂对象参数 无注解(直接用对象接收) ServletModelAttributeMethodProcessor 接收多个键值对,自动封装为 Java 对象(如 User

3. 底层解析原理

(1)单个参数解析(@RequestParam + RequestParamMapMethodArgumentResolver
  1. 提取数据:处理器从 HttpServletRequest 中提取参数(优先从请求体获取表单数据,其次从 URL 查询字符串获取);
  2. 参数匹配:根据 @RequestParamvalue 属性,匹配请求中的键名(如 @RequestParam("name") 匹配 name=张三);
  3. 类型转换:通过 ConversionService 将提取的字符串参数转换为目标类型(如 StringInteger);
  4. 参数注入:将转换后的值注入到 Controller 方法参数。
(2)对象参数解析(无注解 + ServletModelAttributeMethodProcessor
  1. 创建对象实例:通过反射实例化目标对象(如 User),要求对象必须有无参构造器
  2. 提取参数集合:将请求中的所有键值对封装为 MutablePropertyValues 对象;
  3. 属性匹配与填充:根据 “请求键名” 与 “对象属性名” 的匹配关系(如 userName 对应 setUserName()),通过反射填充属性值;
  4. 数据转换与校验:若属性类型不匹配(如 StringDate),调用 ConversionService 转换;若有校验注解(如 @NotNull),触发 Validator 校验。

4. 实战示例

(1)接收单个 / 多个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FormParamController {

// 1. GET 请求:URL 查询字符串(?name=张三&age=25)
@GetMapping("/user/query")
public String queryUser(
@RequestParam("name") String userName, // 匹配 "name" 参数
@RequestParam(defaultValue = "18") Integer age) { // 可选参数,默认值 18
return "姓名:" + userName + ",年龄:" + age;
}

// 2. POST 请求:表单提交(请求体为 name=李四&gender=男)
@PostMapping("/user/save")
public String saveUser(
@RequestParam String name, // 键名与参数名一致,可省略 value
@RequestParam(required = false) String gender) { // 非必填参数
return "保存用户:" + name + ",性别:" + (gender == null ? "未知" : gender);
}
}
(2)接收对象参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

// 表单参数:name=王五&age=30&gender=男 → 自动封装为 User 对象
@PostMapping("/user/create")
public String createUser(User user) { // 无注解,直接用对象接收
return "创建用户:" + user.getName() + ",年龄:" + user.getAge();
}

// 目标对象(需无参构造器、getter/setter)
static class User {
private String name;
private Integer age;
private String gender;

public User() {} // 必须有无参构造器

// getter/setter 省略
}
}

5. 常见问题

  • 参数绑定失败:若请求参数名与对象属性名不一致,需用 @RequestParam 显式映射(如 @RequestParam("user_name") String name);
  • URL 编码问题:中文参数需确保前端编码与后端解码一致(默认 UTF-8,若出现乱码,需配置 CharacterEncodingFilter)。

application/json:JSON 请求体格式

这是前后端分离架构的首选格式,适用于传输复杂数据(如嵌套对象、数组),数据以 JSON 格式存放在请求体中,而非 URL 或表单。

1. 格式特点

  • 数据位置:请求体(RequestBody);
  • 数据结构:支持嵌套对象(如 {"user":{"name":"张三","age":25}})、数组等复杂结构;
  • 编码方式:UTF-8(默认,无需 URL 编码)。

2. 接收方式与参数处理器

接收场景 注解 / 方式 核心参数处理器 依赖组件
复杂 JSON 数据 @RequestBody RequestResponseBodyMethodProcessor MappingJackson2HttpMessageConverter(依赖 Jackson)

3. 底层解析原理

  1. 判断格式RequestResponseBodyMethodProcessor 先检查请求头 Content-Type 是否为 application/json,确认后触发 JSON 解析;
  2. 读取请求体:通过 HttpInputMessage 读取请求体中的 JSON 字符串;
  3. JSON 转 Java 对象:调用 MappingJackson2HttpMessageConverter,底层通过 Jackson 的 ObjectMapper 将 JSON 字符串反序列化为目标 Java 对象(如 UserList<User>);
  4. 参数注入:将反序列化后的对象注入到 Controller 方法参数。
关键依赖:Jackson

Spring MVC 默认使用 Jackson 处理 JSON 转换,需添加依赖:

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>

4. 实战示例

(1)接收单个 JSON 对象
1
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
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class JsonController {

// 请求体:{"name":"张三","age":25,"address":{"city":"北京","street":"科技园路"}}
@PostMapping(value = "/json/user", consumes = "application/json")
public String receiveUser(@RequestBody User user) { // @RequestBody 标注 JSON 解析
return "接收用户:" + user.getName() + ",城市:" + user.getAddress().getCity();
}

// 嵌套对象(支持复杂结构)
static class User {
private String name;
private Integer age;
private Address address; // 嵌套 Address 对象

// 无参构造器、getter/setter 省略
}

static class Address {
private String city;
private String street;

// 无参构造器、getter/setter 省略
}
}
(2)接收 JSON 数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
public class JsonArrayController {

// 请求体:[{"name":"张三","age":25},{"name":"李四","age":30}]
@PostMapping("/json/userList")
public String receiveUserList(@RequestBody List<User> userList) {
return "接收用户数量:" + userList.size() + ",第一个用户:" + userList.get(0).getName();
}

// User 类同上
}
(3)自定义 JSON 解析(如日期格式)

若默认解析不满足需求(如日期格式为 yyyy/MM/dd),可配置 ObjectMapper

1
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
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Configuration
public class JsonConfig {
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
// 配置日期格式(LocalDateTime 转为 "yyyy/MM/dd HH:mm:ss")
JavaTimeModule module = new JavaTimeModule();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
objectMapper.registerModule(module);
// 允许空对象序列化
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

return new MappingJackson2HttpMessageConverter(objectMapper);
}
}

5. 与 @RequestParam 的核心区别

维度 @RequestBody @RequestParam
数据位置 请求体(RequestBody) URL 查询字符串或表单(Request Parameter)
适用格式 application/json application/x-www-form-urlencoded
数据结构 支持复杂嵌套对象、数组 仅支持扁平键值对
依赖组件 MappingJackson2HttpMessageConverter ConversionService

URL 路径参数:REST 风格占位符格式

适用于 RESTful API,参数直接嵌入 URL 路径中(如 /user/123 中的 123 表示用户 ID),通过占位符 {} 定义参数位置。

1. 格式特点

  • 数据位置:URL 路径(如 /user/{userId});
  • 语法规则:用 {参数名} 定义占位符,支持正则表达式(如 /user/{userId:\\d+} 仅匹配数字 ID);
  • 适用场景:RESTful 风格的资源定位(如查询单个资源、删除资源)。

2. 接收方式与参数处理器

接收场景 注解 / 方式 核心参数处理器 关键功能
URL 路径参数 @PathVariable PathVariableMethodArgumentResolver 从 URL 路径中提取占位符参数,支持正则匹配

3. 底层解析原理

  1. 匹配 URL 模板HandlerMapping(如 RequestMappingHandlerMapping)将请求 URL 与 Controller 方法的 @RequestMapping 路径模板匹配(如 /user/{userId} 匹配 /user/123);
  2. 提取参数值PathVariableMethodArgumentResolver 从匹配结果中提取占位符对应的参数值(如 userId=123);
  3. 类型转换:通过 ConversionService 将字符串参数值转换为目标类型(如 StringLong);
  4. 参数注入:将转换后的值注入到 @PathVariable 标注的方法参数。

4. 实战示例

(1)基础用法:单个路径参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rest/user")
public class RestUserController {

// URL:/rest/user/123 → 提取 userId=123
@GetMapping("/{userId}")
public String getUserById(@PathVariable("userId") Long id) { // value 与占位符一致
return "查询用户 ID:" + id;
}

// 简化:参数名与占位符一致时,可省略 value
@GetMapping("/name/{userName}")
public String getUserByName(@PathVariable String userName) { // 匹配 {userName}
return "查询用户姓名:" + userName;
}
}
(2)高级用法:正则表达式限制参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rest")
public class RegexPathVariableController {

// 仅匹配数字 ID(正则 \\d+),非数字会 404
@GetMapping("/user/{userId:\\d+}")
public String getUserByNumId(@PathVariable Long userId) {
return "数字 ID 用户:" + userId;
}

// 仅匹配字母名称(正则 [a-zA-Z]+),含数字会 404
@GetMapping("/user/name/{userName:[a-zA-Z]+}")
public String getUserByLetterName(@PathVariable String userName) {
return "字母名称用户:" + userName;
}
}
(3)多个路径参数
1
2
3
4
5
6
7
@GetMapping("/order/{orderId}/item/{itemId}")
public String getOrderItem(
@PathVariable Long orderId,
@PathVariable Long itemId) {
return "订单 ID:" + orderId + ",商品 ID:" + itemId;
}
// URL:/order/1001/item/2001 → 输出:订单 ID:1001,商品 ID:2001

multipart/form-data:文件上传格式

专门用于文件上传(如图片、文档),支持同时传输文件和普通键值对,数据以 “分隔符” 分割不同部分,不进行 URL 编码。

1. 格式特点

  • 数据位置:请求体,按 “部分(Part)” 分割(文件为一个 Part,普通参数为另一个 Part);
  • 适用场景:文件上传(单个 / 多个文件),或文件 + 普通参数混合传输;
  • 依赖配置:需开启 Spring MVC 的文件上传支持(默认开启,需配置最大文件大小)。

2. 接收方式与参数处理器

Spring MVC 支持多种方式接收文件,对应不同的参数处理器:

接收场景 注解 / 方式 核心参数处理器 适用场景
单个 / 多个文件 @RequestPart + MultipartFile RequestPartMethodArgumentResolver 推荐,支持文件 + 复杂参数(如 JSON)混合传输
单个 / 多个文件 @RequestParam + MultipartFile RequestParamMethodArgumentResolver 兼容传统文件上传,仅支持简单键值对 + 文件
单个 / 多个文件 无注解 + MultipartFile RequestParamMethodArgumentResolver 简化写法,参数名需与前端表单名一致

3. 底层解析原理

  1. 解析请求体:Spring MVC 通过 MultipartResolver(默认 StandardServletMultipartResolver)解析 multipart/form-data 格式的请求体,将每个 “Part” 封装为 MultipartFile 对象;
  2. 匹配参数:
    • @RequestPart:按 “Part 名称” 匹配文件(支持非文件 Part,如 JSON 字符串);
    • @RequestParam:按 “表单字段名” 匹配文件,仅处理文件类型的 Part;
  3. 参数注入:将 MultipartFile 对象注入到 Controller 方法参数,开发者可通过 MultipartFile 的方法操作文件(如获取文件名、大小、保存文件)。

4. 实战示例

(1)基础配置:文件上传大小限制

springmvc.xml 或 Java 配置中设置文件上传的最大大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- XML 配置 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
<!-- 可选:默认编码 -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>

<!-- 配置 Servlet 上下文参数(web.xml) -->
<context-param>
<param-name>spring.http.multipart.maxFileSize</param-name>
<param-value>10MB</param-value> <!-- 单个文件最大 10MB -->
</context-param>
<context-param>
<param-name>spring.http.multipart.maxRequestSize</param-name>
<param-value>50MB</param-value> <!-- 整个请求最大 50MB -->
</context-param>
(2)单个文件上传(@RequestPart 推荐)
1
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
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;

@RestController
@RequestMapping("/upload")
public class FileUploadController {

// 前端表单:<input type="file" name="avatar">
@PostMapping(value = "/single", consumes = "multipart/form-data")
public String uploadSingleFile(
@RequestPart("avatar") MultipartFile avatar, // 匹配 Part 名称 "avatar"
@RequestPart("user") User user) { // 同时接收 JSON 格式的 User 对象(Part 名称 "user")

// 处理文件(保存到本地)
if (!avatar.isEmpty()) {
String fileName = avatar.getOriginalFilename(); // 获取原始文件名
try {
avatar.transferTo(new File("D:/uploads/" + fileName)); // 保存文件
} catch (IOException e) {
return "文件上传失败:" + e.getMessage();
}
}

return "文件上传成功!文件名:" + avatar.getOriginalFilename() + ",用户:" + user.getName();
}

// User 类(用于接收 JSON 格式的 Part)
static class User {
private String name;
private Integer age;
// 无参构造器、getter/setter 省略
}
}
(3)多个文件上传(List<MultipartFile>
1
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
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;

@RestController
@RequestMapping("/upload")
public class MultiFileUploadController {

// 前端表单:<input type="file" name="files" multiple>(multiple 支持多选)
@PostMapping("/multiple")
public String uploadMultipleFiles(@RequestPart("files") List<MultipartFile> files) {
int successCount = 0;
for (MultipartFile file : files) {
if (!file.isEmpty()) {
try {
file.transferTo(new File("D:/uploads/" + file.getOriginalFilename()));
successCount++;
} catch (IOException e) {
// 忽略单个文件失败,继续处理其他文件
}
}
}
return "上传完成!成功:" + successCount + " 个,总数量:" + files.size();
}
}

5. @RequestPart vs @RequestParam 区别

维度 @RequestPart @RequestParam
支持 Part 类型 文件、JSON 字符串等复杂类型 仅文件或简单键值对
内容解析 支持 HttpMessageConverter(如 JSON 转对象) 仅按字符串或字节流解析
适用场景 文件 + 复杂参数混合传输 传统简单文件上传

参数解析总结与适用场景对比

请求格式 核心注解 / 方式 核心处理器 适用场景
application/x-www-form-urlencoded @RequestParam / 无注解(对象) RequestParamMapMethodArgumentResolver / ServletModelAttributeMethodProcessor 传统表单提交、URL 查询字符串、简单键值对
application/json @RequestBody RequestResponseBodyMethodProcessor 前后端分离、复杂嵌套对象 / 数组传输
URL 路径参数 @PathVariable PathVariableMethodArgumentResolver RESTful API、资源定位(如查询单个资源)
multipart/form-data @RequestPart / @RequestParam + MultipartFile RequestPartMethodArgumentResolver / RequestParamMethodArgumentResolver 文件上传(单个 / 多个)、文件 + 参数混合传输

关键选型建议

  1. 简单表单 / URL 参数:用 @RequestParam 或直接用对象接收;
  2. 前后端分离传复杂数据:用 @RequestBody + JSON 格式;
  3. RESTful API 定位资源:用 @PathVariable
  4. 文件上传:优先用 @RequestPart(支持混合传输),兼容场景用 @RequestParam

欢迎关注我的其它发布渠道