一篇文章搞懂
@PathVariable、@RequestParam、@RequestBody、@ModelAttribute等所有请求参数注解
前言
在 Spring Boot 开发过程中,相信大家都会遇到这样的场景:前端传来各种各样的请求参数,有的是在 URL 路径中,有的是在 URL "?" 后面,还有的是放在请求体中的 JSON 数据。我们应该如何在后端优雅地接收这些参数?Spring Boot 提供了一系列注解来帮助我们轻松完成请求参数的绑定,本文将对其中最常用的注解进行详细讲解,包括它们的用法、区别以及最佳实践。
掌握这几个注解,可以说 Web 开发中 90% 的参数接收场景都可以轻松应对。现在就让我们一起来学习吧!
一、@PathVariable:路径变量
1.1 简介
@PathVariable 注解用于从请求 URL 路径中获取变量的值。它非常适用于 RESTful 风格的接口设计,将 URL 路径中的某一部分作为参数传递给控制器方法。
比如,我们在设计获取用户信息的接口时,通常会将用户 ID(唯一标识)放在 URL 路径中:/api/users/123,其中 123 就是路径变量。
1.2 基本用法
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// 访问示例:GET /api/users/100
@GetMapping("/{userId}")
public ResponseEntity<User> getUserById(@PathVariable Long userId) {
// userId 的值自动绑定为 100
return ResponseEntity.ok(userService.getUserById(userId));
}
}
1.3 多个路径变量
一个 URL 路径中可以包含多个路径变量:
java
// 访问示例:GET /api/users/100/orders/200
@GetMapping("/{userId}/orders/{orderId}")
public ResponseEntity<Order> getUserOrder(@PathVariable Long userId, @PathVariable Long orderId) {
// userId=100, orderId=200
return ResponseEntity.ok(orderService.findByUserAndOrder(userId, orderId));
}
1.4 参数名不一致时的处理
如果方法参数名与 URL 中的变量名不一致,可以通过 @PathVariable 的 value 属性显式指定:
java
@GetMapping("/{userId}")
public ResponseEntity<User> getUserById(@PathVariable("userId") Long id) {
// 将 {userId} 的值绑定到 id 参数上
return ResponseEntity.ok(userService.getUserById(id));
}
1.5 @PathVariable 常用属性
| 属性 | 说明 | 示例 |
|---|---|---|
value / name |
绑定参数的名称,默认不传递时绑定为同名形参 | @PathVariable("uid") |
required |
参数是否必须,默认为 true | @PathVariable(required = false) |
需要注意的是,如果 required=true 而路径中没有对应的变量,请求将会失败。
1.6 使用正则表达式精确匹配
可以对 URL 变量进行更精确的定义:
java
// 只允许大小写字母、数字和下划线
@GetMapping("/user/{username:[a-zA-Z0-9_]+}")
public String getUserProfile(@PathVariable String username) {
return "User: " + username;
}
这样一来,不合法的 URL 路径将直接被 Spring MVC 拒绝。
二、@RequestParam:请求参数(查询参数)
2.1 简介
@RequestParam 注解用于从 HTTP 请求中获取查询参数 (Query Parameters),这些参数通常出现在 URL 的 ? 之后,格式为 key=value,多个参数之间用 & 连接。
典型的使用场景包括:GET 请求的分页参数(page、size)、搜索关键字等。
2.2 基本用法
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// 访问示例:GET /api/users/search?username=zhang
@GetMapping("/search")
public ResponseEntity<List<User>> searchUsers(@RequestParam String username) {
return ResponseEntity.ok(userService.searchByUsername(username));
}
}
当参数名与方法参数名相同时,可以省略 value 属性。
2.3 参数可选与默认值
@RequestParam 提供了 required 和 defaultValue 两个重要属性来处理可选参数:
java
@GetMapping("/greet")
public String greet(
@RequestParam(name = "name", required = false, defaultValue = "World") String name
) {
// 当请求没有提供 name 参数时,使用默认值 "World"
return "Hello, " + name + "!";
}
// 访问 /greet -> Hello, World!
// 访问 /greet?name=John -> Hello, John!
属性详解:
required:参数是否必须,默认为true。如果设为false,表示参数可选,不传时参数值为null(对于基本数据类型需要特别注意空指针问题)defaultValue:参数的默认值,一旦设置了defaultValue,required的设置会失效value/name:指定请求参数名称,如果不指定则默认使用方法参数名
2.4 @RequestParam 接收数组/集合
java
// 访问示例:GET /api/users?ids=1,2,3&ids=4
@GetMapping
public ResponseEntity<List<User>> getUsers(@RequestParam List<Long> ids) {
// ids = [1,2,3,4]
return ResponseEntity.ok(userService.findByIds(ids));
}
当请求中有多个同名参数时,Spring Boot 会自动将其收集为数组或集合。
2.5 @RequestParam vs 不加注解的区别
如果在控制器方法参数上不加任何注解,Spring Boot 也会尝试将请求参数绑定到该参数上,且默认值为 required=false。但为了代码可读性和明确性,建议显式使用 @RequestParam。
三、@RequestBody:请求体
3.1 简介
@RequestBody 注解用于接收 HTTP 请求体(Request Body)中的数据,并自动将其转换为 Java 对象。在前后端分离的架构中,这是最常用的注解之一,通常用来接收 POST/PUT 请求中携带的 JSON 或 XML 数据。
3.2 基本用法
首先定义一个实体类:
java
@Data
public class UserRequest {
private String username;
private String email;
private Integer age;
}
然后在控制器中使用 @RequestBody:
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// 请求示例:POST /api/users
// 请求体:{"username": "zhang", "email": "zhang@example.com", "age": 25}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody UserRequest userRequest) {
// Spring Boot 自动将 JSON 字符串映射到 userRequest 对象
User user = userService.createUser(userRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
}
3.3 底层原理:HttpMessageConverter
@RequestBody 的自动转换能力依赖于 Spring Boot 的 HTTP 消息转换器(HttpMessageConverter) 机制。Spring Boot 默认集成了 Jackson,它会自动将请求体中的 JSON 反序列化为 Java 对象,同时也支持 XML、表单数据等多种格式:
- 反序列化:将 HTTP 请求体的字节流(JSON/XML)转换为 Java 对象
- 序列化 :将 Java 返回值对象转换为 HTTP 响应体的字节流(由
@ResponseBody配合完成)
3.4 接收复杂嵌套对象
java
@Data
public class OrderRequest {
private Long orderId;
private List<OrderItem> items; // 嵌套的 List
private Address shippingAddress; // 嵌套的对象
}
@PostMapping("/order")
public ResponseEntity<String> createOrder(@RequestBody OrderRequest orderRequest) {
// 自动解析嵌套的 JSON 结构
return ResponseEntity.ok("Order created with " + orderRequest.getItems().size() + " items");
}
3.5 @RequestBody 使用注意事项
- 不能用于 GET 请求 :因为 GET 请求没有请求体,用
@RequestBody会报错 - 每个 Controller 方法只能有一个
@RequestBody:请求体的输入流只能被读取一次 - Content-Type 必须匹配 :请求需要携带
Content-Type: application/json等匹配的媒体类型 - 对象属性缺失的处理 :如果 JSON 中缺少实体类的某些属性,这些属性会被赋值为
null
3.6 @ResponseBody 小贴士
@ResponseBody 通常和 @RequestBody 成对出现,用于将 Java 对象序列化为 JSON 返回给前端。而 @RestController 注解实际上就是 @Controller 和 @ResponseBody 的结合体,所以我们在使用 @RestController 时,所有方法的返回值会自动被序列化为 JSON 响应体,无需再单独添加 @ResponseBody。
四、@ModelAttribute:模型属性绑定
4.1 简介
@ModelAttribute 注解用于将多个请求参数绑定到一个 Java 对象(命令对象)上,非常适用于处理表单提交场景。它可以出现在两个位置:
- 方法参数上:将请求参数绑定到对象的属性上
- 方法上(非请求处理方法):在执行 Controller 方法之前,预先将数据添加到模型中供视图使用
4.2 方法参数中使用(最常用)
java
@Data
public class UserForm {
private String username;
private String password;
private String email;
}
@PostMapping("/register")
public String register(@ModelAttribute UserForm userForm, Model model) {
// userForm 的 username、password、email 属性已自动绑定表单数据
userService.register(userForm);
model.addAttribute("message", "注册成功");
return "result";
}
4.3 @ModelAttribute vs @RequestBody 的区别
这两个注解很容易混淆,但本质上有着完全不同的处理方式:
| 对比维度 | @ModelAttribute | @RequestBody |
|---|---|---|
| 数据来源 | 请求参数(Query Parameters + Form Data) | 请求体(Request Body) |
| 数据格式 | application/x-www-form-urlencoded |
application/json / application/xml |
| 数据绑定 | 简单数据绑定,逐个属性映射 | 使用消息转换器进行序列化/反序列化 |
| 适用场景 | 传统 Web 表单提交 | RESTful API 前后端分离 |
简单来说:传统表单用 @ModelAttribute,现代 REST API 用 @RequestBody。
4.4 接收表单数据的方式对比
在 Spring Boot 中接收表单数据有三种常见方式:
方式一:使用 @RequestParam 逐个接收
java
@PostMapping("/submit")
public String submit(@RequestParam("name") String name, @RequestParam("email") String email) {
// 处理...
}
方式二:不加注解(Spring 自动绑定)
java
@PostMapping("/submit")
public String submit(String name, String email) {
// 处理...
}
方式三:使用 @ModelAttribute 封装为对象
java
@PostMapping("/submit")
public String submit(@ModelAttribute User user) {
// 处理...
}
当参数较多时,使用 @ModelAttribute 封装为对象是更优雅的选择。
五、其他常用注解
5.1 @RequestHeader:获取请求头
@RequestHeader 用于获取 HTTP 请求头中的信息。虽然业务代码中不太常用,但在认证鉴权、链路追踪等基础设施中不可或缺:
java
@GetMapping("/user")
public ResponseEntity<User> getUserList(
@RequestHeader("Authorization") String authToken,
@RequestHeader Map<String, String> allHeaders // 获取所有请求头
) {
// 验证 token 并返回数据
return ResponseEntity.ok(userRepo.findAll());
}
@RequestHeader 也支持 required 和 defaultValue 属性,用法与 @RequestParam 类似。
5.2 @CookieValue:获取 Cookie 值
当需要与客户端保持会话状态时,@CookieValue 可以方便地读取 Cookie 中的值:
java
@GetMapping("/welcome")
public String welcome(@CookieValue(name = "sessionId", defaultValue = "none") String sessionId) {
return "Session ID: " + sessionId;
}
5.3 @MatrixVariable:矩阵变量
@MatrixVariable 是一种比较少见(但在某些国外系统中会遇到)的参数形式,参数使用 ; 分隔:
java
// 访问示例:GET /books/reviews;isbn=1234;topN=5
@GetMapping("/books/reviews")
public List<BookReview> getBookReviews(
@MatrixVariable String isbn,
@MatrixVariable Integer topN
) {
return bookReviewsLogic.getTopNReviewsByIsbn(isbn, topN);
}
5.4 @RequestAttribute:获取请求域属性
@RequestAttribute 用于获取通过请求转发设置的 request 域属性值,主要用于请求转发(forward)场景:
java
// 在过滤器或拦截器中 setAttribute
request.setAttribute("userInfo", userInfo);
// 在 Controller 中获取
@GetMapping("/profile")
public String getProfile(@RequestAttribute("userInfo") UserInfo userInfo) {
return "user: " + userInfo.getUsername();
}
六、注解对比总结
| 注解 | 数据来源 | 适用请求方法 | 适用场景 | 必填属性 |
|---|---|---|---|---|
@PathVariable |
URL 路径中(如 /user/{id}) |
GET / POST / PUT / DELETE | RESTful 资源标识 | required |
@RequestParam |
查询参数(?key=value) |
GET(为主)/ POST | 分页、筛选、搜索 | required, defaultValue |
@RequestBody |
请求体(JSON/XML) | POST / PUT / PATCH | 创建/更新复杂对象 | required |
@ModelAttribute |
表单/查询参数 | GET / POST | 传统表单提交 | 不常用 |
@RequestHeader |
HTTP 请求头 | 所有方法 | 认证 Token、TraceID | required, defaultValue |
@CookieValue |
Cookie | 所有方法 | Session ID、用户偏好 | required, defaultValue |
@MatrixVariable |
矩阵变量(;key=value) |
所有方法 | 特殊 URL 设计 | required, defaultValue |
@RequestAttribute |
Request 域(setAttribute) |
所有方法 | 请求转发数据传递 | required |
快速选择指南
- 路径中的唯一标识 (如用户 ID、订单号)→
@PathVariable - GET 请求的筛选/分页参数 (如
?page=1&size=10)→@RequestParam - POST/PUT 请求的 JSON 数据 (前后端分离)→
@RequestBody - 传统表单提交 (参数多且分散)→
@ModelAttribute或@RequestParam - 需要读取请求头中的元数据 (如 Token)→
@RequestHeader - 需要读取浏览器 Cookie (如 Session ID)→
@CookieValue
七、多注解组合使用
在实际业务中,有时需要同时接收多种类型的参数,例如:从路径中获取用户 ID、从查询参数中获取筛选条件、从请求体中获取修改的数据:
java
@PutMapping("/users/{userId}/profile")
public ResponseEntity<User> updateUserProfile(
@PathVariable Long userId, // 从路径中获取用户 ID
@RequestParam(required = false) String flag, // 从查询参数中获取标志
@RequestBody UserProfileRequest request // 从请求体中获取更新数据
) {
// 三个参数各司其职
userService.updateProfile(userId, request, flag);
return ResponseEntity.ok().build();
}
需要注意的是,@PathVariable、@RequestParam 和 @RequestBody 都是互不冲突的,可以根据需要在同一个方法中组合使用多个。
八、常见问题与避坑指南
8.1 @RequestParam required=true 但参数缺失
现象:请求中没有携带必填参数,返回 400 错误。
解决 :将 required 设为 false 或提供 defaultValue。
8.2 @RequestBody 接收的参数为 null
可能原因:
- 请求的
Content-Type不是application/json - JSON 字段名与 Java 对象属性名不匹配
- 实体类缺少无参构造方法或 Getter/Setter 方法
8.3 @PathVariable 参数中包含斜杠被截断
默认情况下,路径变量不能包含 URL 分隔符 /,如果需要接收包含斜杠的值,可以使用正则表达式:
java
@GetMapping("/files/{path:[a-zA-Z0-9/]+}")
public String getFile(@PathVariable String path) { ... }
8.4 基本数据类型与 required=false 的坑
当参数为基本数据类型(如 int、long)且 required=false 时,如果请求中没有该参数,会有空指针问题。应使用包装类(Integer、Long)来接收可选参数。
8.5 defaultValue 与 required 同时使用的注意事项
设置 defaultValue 后,required 的设置会失效。也就是说,即使你写了 required=true 并设置了 defaultValue,参数不传时也不会报错,会直接使用默认值。
结语
本文详细介绍了 Spring Boot 中用于接收请求参数的核心注解,从最常用的 @PathVariable、@RequestParam、@RequestBody,到表单绑定的 @ModelAttribute,再到请求头、Cookie 等相对进阶的注解,涵盖了日常开发中 90% 以上的参数接收场景。
在实际开发中,建议大家:
- 优先使用 RESTful 风格的 URL 设计 ,合理使用
@PathVariable标识资源 - 前后端分离项目统一使用
@RequestBody传递复杂数据 - 大量且相似的表单参数 ,使用
@ModelAttribute封装为对象 - 保持代码可读性,即使是简单参数也建议显式使用注解