一、核心概念
Spring Boot 通过特定的注解将 HTTP 请求中的数据绑定到控制器方法的参数上。这是构建 Web API 的核心能力。
1. 核心注解
注解 | 作用域 | 说明 | |
---|---|---|---|
@RequestParam |
请求参数 (Query Parameters) | 绑定 URL 查询字符串(?key=value )或表单数据(application/x-www-form-urlencoded )中的参数。 |
|
@PathVariable |
路径变量 (Path Variables) | 绑定 URL 路径模板(/users/{id} )中 {} 占位符的值。 |
|
@RequestBody |
请求体 (Request Body) | 绑定 HTTP 请求体(如 JSON、XML)的内容到一个对象。通常用于 POST、PUT 请求。 |
2. 数据来源
@RequestParam
:GET /api/users?name=John&age=30
->name=John
,age=30
@PathVariable
:GET /api/users/123
(映射到/api/users/{id}
) ->id=123
@RequestBody
:POST /api/users
+ JSON Body{ "name": "John", "age": 30 }
3. 控制器基础
@Controller
: 通用控制器,方法返回值默认为视图名。@RestController
:@Controller
+@ResponseBody
,方法返回值直接作为响应体(如 JSON),是 REST API 的首选。
二、操作步骤(非常详细)
步骤 1:创建基础控制器
import org.springframework.web.bind.annotation.*;
@RestController // 所有方法返回值作为响应体(如 JSON)
@RequestMapping("/api") // 类级别公共前缀
public class ParameterController {
// 在这里添加处理方法
}
步骤 2:使用 @RequestParam
接收查询参数
2.1 基本用法
// GET /api/search?query=spring
@GetMapping("/search")
public String search(@RequestParam String query) {
return "Searching for: " + query;
}
2.2 设置默认值和可选性
// GET /api/search?query=spring&page=2&size=10
// 或 GET /api/search?query=spring (page=0, size=20 默认)
@GetMapping("/search")
public Map<String, Object> search(
@RequestParam String query,
@RequestParam(defaultValue = "0") int page, // 默认值 0
@RequestParam(defaultValue = "20", required = false) int size // 可选,有默认值
) {
Map<String, Object> result = new HashMap<>();
result.put("query", query);
result.put("page", page);
result.put("size", size);
// ... 执行搜索逻辑
return result;
}
2.3 接收数组或集合
// GET /api/users?role=admin&role=user
@GetMapping("/users")
public String getUsersByRoles(@RequestParam List<String> role) {
return "Roles: " + role; // role = ["admin", "user"]
}
// GET /api/ids?id=1&id=2&id=3
@GetMapping("/ids")
public String getIds(@RequestParam Long[] id) {
return "IDs: " + Arrays.toString(id);
}
2.4 自定义参数名
// GET /api/user?user_name=Alice
@GetMapping("/user")
public String getUser(@RequestParam("user_name") String userName) {
return "Hello, " + userName;
}
步骤 3:使用 @PathVariable
接收路径变量
3.1 基本用法
// GET /api/users/123
@GetMapping("/users/{id}")
public String getUser(@PathVariable Long id) {
return "Fetching user with ID: " + id;
}
3.2 多个路径变量
// GET /api/users/123/orders/456
@GetMapping("/users/{userId}/orders/{orderId}")
public String getOrder(
@PathVariable Long userId,
@PathVariable("orderId") Long id // 可自定义参数名映射
) {
return "Fetching order " + id + " for user " + userId;
}
3.3 使用正则表达式限制
// GET /api/users/123abc 会 404,因为 id 必须是数字
// GET /api/users/123 是有效的
@GetMapping("/users/{id:\\d+}") // :\\d+ 表示 id 必须匹配正则 \\d+ (一个或多个数字)
public String getUser(@PathVariable Long id) {
return "User ID (must be number): " + id;
}
步骤 4:使用 @RequestBody
接收请求体
4.1 创建数据模型 (POJO)
public class User {
private Long id;
private String name;
private Integer age;
private String email;
// Constructors, Getters, Setters, toString...
public User() {}
public User(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// ... getters and setters
}
4.2 接收 JSON 请求体
// POST /api/users
// Body: { "name": "John Doe", "age": 30, "email": "john@example.com" }
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
// 模拟保存
user.setId(System.currentTimeMillis()); // 简单模拟生成ID
System.out.println("Created user: " + user);
// 返回 201 Created 和新资源
return ResponseEntity.created(URI.create("/api/users/" + user.getId())).body(user);
}
4.3 接收简单类型请求体 (较少见)
// POST /api/message
// Body: "Hello, World!"
@PostMapping("/message")
public String receiveMessage(@RequestBody String message) {
return "Received message: " + message;
}
4.4 结合 @Valid
进行数据校验
import jakarta.validation.Valid;
import org.springframework.validation.BindingResult;
// POST /api/users
// Body: { "name": "", "age": -5, "email": "invalid-email" }
@PostMapping("/users/validated")
public ResponseEntity<?> createUserValidated(
@Valid @RequestBody User user, // @Valid 触发校验
BindingResult result // 必须紧跟在 @Valid 参数后,用于捕获错误
) {
if (result.hasErrors()) {
// 处理校验错误
Map<String, String> errors = new HashMap<>();
result.getFieldErrors().forEach(fe ->
errors.put(fe.getField(), fe.getDefaultMessage())
);
return ResponseEntity.badRequest().body(errors);
}
// 保存用户...
user.setId(System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
注意:需要添加
spring-boot-starter-validation
依赖。
步骤 5:组合使用多种参数
// PUT /api/users/123?notify=true
// Body: { "name": "Updated Name", "email": "updated@example.com" }
@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id, // 路径变量
@RequestParam(required = false, defaultValue = "false") boolean notify, // 查询参数
@Valid @RequestBody User userDetails, // 请求体
BindingResult result
) {
if (result.hasErrors()) {
return ResponseEntity.badRequest().build();
}
// 更新逻辑...
userDetails.setId(id); // 确保ID一致
System.out.println("Update user " + id + ", notify: " + notify + ", details: " + userDetails);
return ResponseEntity.ok(userDetails);
}
三、常见错误与解决方案
错误 | 原因 | 解决方案 |
---|---|---|
400 Bad Request : MissingServletRequestParameterException |
必需的 @RequestParam 或 @PathVariable 未提供 |
检查 URL 路径和查询参数,确保 required=true 的参数存在,或设置 defaultValue |
400 Bad Request : HttpMessageNotReadableException |
@RequestBody JSON/XML 格式错误或无法反序列化 |
检查 JSON 结构、字段名、数据类型是否与目标类匹配;确认 Content-Type 头为 application/json |
404 Not Found |
路径变量 {id} 不存在或类型不匹配 |
检查 URL 路径是否正确;确保路径变量类型(如 Long )与 URL 中的值兼容 |
500 Internal Server Error : 混淆 @RequestParam 和 @PathVariable |
期望路径变量却用了查询参数,反之亦然 | 明确区分:路径变量在 @RequestMapping 的 value 中用 {} 定义,用 @PathVariable 注入;查询参数在 URL ? 后,用 @RequestParam 注入 |
BindingResult 必须紧跟 @Valid |
BindingResult 参数位置错误 |
BindingResult 参数必须紧跟在 @Valid 注解的参数之后 |
@RequestBody 参数只能有一个 |
方法中有多个 @RequestBody |
一个方法只能有一个 @RequestBody 参数。将多个简单参数合并到一个 DTO 类中 |
四、注意事项
@RequestParam
与表单数据:也用于处理application/x-www-form-urlencoded
(传统表单提交) 和multipart/form-data
(文件上传) 的参数。@PathVariable
与 URL 编码:路径变量的值会自动进行 URL 解码。@RequestBody
与MessageConverter
:Spring 使用HttpMessageConverter
(如Jackson2ObjectMapper
) 进行 JSON <-> Object 转换。确保依赖(如jackson-databind
)存在。- 类型转换:Spring 会自动尝试将字符串参数(
@RequestParam
,@PathVariable
)转换为目标类型(int
,Long
,boolean
,Date
等)。失败会抛出400
错误。 required=false
:对于@RequestParam
,required=false
表示参数可选,未提供时参数值为null
(引用类型)或使用defaultValue
。@RequestBody
与null
:如果请求体为空且参数类型不是String
或byte[]
,通常会抛出400
错误。可使用@RequestBody(required = false)
允许空体(此时参数为null
)。
五、使用技巧
- DTO (Data Transfer Object) 模式:为
@RequestBody
创建专门的 DTO 类,避免直接使用领域模型(Entity),实现关注点分离和安全。 @RequestParam
接收文件:结合MultipartFile
使用@RequestParam
接收上传的文件。@PostMapping("/upload") public String handleFileUpload(@RequestParam("file") MultipartFile file) { ... }
@RequestBody
接收Map
:当结构不确定时,可接收为Map<String, Object>
。@PostMapping("/dynamic") public String handleDynamic(@RequestBody Map<String, Object> data) { ... }
@ModelAttribute
:用于将请求参数(查询参数和表单字段)绑定到一个对象的属性上。常用于表单提交。public class LoginForm { private String username; private String password; // getters, setters } @PostMapping("/login") public String login(@ModelAttribute LoginForm form) { ... } // GET /login?username=john&password=123 也能绑定
@RequestHeader
:接收请求头信息。@GetMapping("/info") public String getInfo(@RequestHeader("User-Agent") String userAgent) { ... }
@CookieValue
:接收 Cookie 值。@GetMapping("/greet") public String greet(@CookieValue("JSESSIONID") String sessionId) { ... }
六、最佳实践
- 明确区分参数来源:清晰理解
@RequestParam
(查询/表单),@PathVariable
(路径),@RequestBody
(主体) 的使用场景。 - 为
@RequestParam
设置默认值:对于分页、排序等可选参数,使用defaultValue
提供合理的默认行为。 - 使用 DTO 接收
@RequestBody
:提高代码可维护性、安全性和灵活性。 - 强制校验输入:对
@RequestBody
和关键的@RequestParam
/@PathVariable
使用@Valid
+ Bean Validation 注解(@NotNull
,@Size
,@Email
,@Pattern
等)。 - 提供清晰的错误信息:通过
BindingResult
或全局异常处理器(@ControllerAdvice
)返回结构化的校验错误信息。 - 文档化 API:使用 Swagger/OpenAPI 明确描述每个参数的来源、类型、是否必需等。
- 安全性:避免在
@PathVariable
或@RequestParam
中传递敏感信息(如密码、令牌)。对输入进行验证和清理,防止注入攻击。
七、性能优化
@RequestBody
反序列化性能:- 使用高效的 JSON 库(如 Jackson,默认且高效)。
- 避免在 DTO 中包含不必要的大字段或深层嵌套对象。
- 考虑使用
@JsonIgnore
或@JsonView
在反序列化时忽略不需要的字段。
@RequestParam
和@PathVariable
解析:解析开销通常很低,但复杂的正则表达式(@PathVariable
)可能有轻微影响。保持路径模式简单。- 对象创建:频繁创建 DTO 实例是正常的。JVM 的新生代垃圾回收对此类短生命周期对象非常高效。
- 连接与线程:确保 Web 服务器配置(如 Tomcat 的
maxThreads
)能处理预期的并发请求量,这比参数绑定本身的优化更重要。 - 缓存:对于基于
@RequestParam
的幂等 GET 请求,考虑使用 Spring Cache 或 HTTP 缓存减少后端计算。
总结
@RequestParam
, @PathVariable
, @RequestBody
是 Spring Boot 接收 HTTP 请求数据的三大支柱。熟练掌握它们的使用场景、属性配置和常见问题,是构建健壮 Web API 的基础。
快速掌握路径:
- 动手编码:创建一个
@RestController
,分别用这三个注解实现几个简单的 API 端点。 - 工具测试:使用 Postman、curl 或浏览器测试,观察不同请求方式(GET 查询参数 vs POST JSON 体)的效果。
- 调试:在方法内设置断点,观察参数是否被正确注入。
- 引入校验:为
@RequestBody
DTO 添加@NotNull
,@Size
等注解,测试无效输入时的响应。 - 阅读文档:查阅 Spring Framework 官方文档中关于 "Annotation Support for Web MVC" 的章节。
掌握这些技巧,你就能高效、安全地处理各种前端或客户端传来的数据了。