一、核心概念

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 期望路径变量却用了查询参数,反之亦然 明确区分:路径变量在 @RequestMappingvalue 中用 {} 定义,用 @PathVariable 注入;查询参数在 URL ? 后,用 @RequestParam 注入
BindingResult 必须紧跟 @Valid BindingResult 参数位置错误 BindingResult 参数必须紧跟在 @Valid 注解的参数之后
@RequestBody 参数只能有一个 方法中有多个 @RequestBody 一个方法只能有一个 @RequestBody 参数。将多个简单参数合并到一个 DTO 类中

四、注意事项

  1. @RequestParam 与表单数据:也用于处理 application/x-www-form-urlencoded (传统表单提交) 和 multipart/form-data (文件上传) 的参数。
  2. @PathVariable 与 URL 编码:路径变量的值会自动进行 URL 解码。
  3. @RequestBodyMessageConverter:Spring 使用 HttpMessageConverter (如 Jackson2ObjectMapper) 进行 JSON <-> Object 转换。确保依赖(如 jackson-databind)存在。
  4. 类型转换:Spring 会自动尝试将字符串参数(@RequestParam, @PathVariable)转换为目标类型(int, Long, boolean, Date 等)。失败会抛出 400 错误。
  5. required=false:对于 @RequestParamrequired=false 表示参数可选,未提供时参数值为 null(引用类型)或使用 defaultValue
  6. @RequestBodynull:如果请求体为空且参数类型不是 Stringbyte[],通常会抛出 400 错误。可使用 @RequestBody(required = false) 允许空体(此时参数为 null)。

五、使用技巧

  1. DTO (Data Transfer Object) 模式:为 @RequestBody 创建专门的 DTO 类,避免直接使用领域模型(Entity),实现关注点分离和安全。
  2. @RequestParam 接收文件:结合 MultipartFile 使用 @RequestParam 接收上传的文件。
    @PostMapping("/upload")
    public String handleFileUpload(@RequestParam("file") MultipartFile file) { ... }
    
  3. @RequestBody 接收 Map:当结构不确定时,可接收为 Map<String, Object>
    @PostMapping("/dynamic")
    public String handleDynamic(@RequestBody Map<String, Object> data) { ... }
    
  4. @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 也能绑定
    
  5. @RequestHeader:接收请求头信息。
    @GetMapping("/info")
    public String getInfo(@RequestHeader("User-Agent") String userAgent) { ... }
    
  6. @CookieValue:接收 Cookie 值。
    @GetMapping("/greet")
    public String greet(@CookieValue("JSESSIONID") String sessionId) { ... }
    

六、最佳实践

  1. 明确区分参数来源:清晰理解 @RequestParam (查询/表单), @PathVariable (路径), @RequestBody (主体) 的使用场景。
  2. @RequestParam 设置默认值:对于分页、排序等可选参数,使用 defaultValue 提供合理的默认行为。
  3. 使用 DTO 接收 @RequestBody:提高代码可维护性、安全性和灵活性。
  4. 强制校验输入:对 @RequestBody 和关键的 @RequestParam/@PathVariable 使用 @Valid + Bean Validation 注解(@NotNull, @Size, @Email, @Pattern 等)。
  5. 提供清晰的错误信息:通过 BindingResult 或全局异常处理器(@ControllerAdvice)返回结构化的校验错误信息。
  6. 文档化 API:使用 Swagger/OpenAPI 明确描述每个参数的来源、类型、是否必需等。
  7. 安全性:避免在 @PathVariable@RequestParam 中传递敏感信息(如密码、令牌)。对输入进行验证和清理,防止注入攻击。

七、性能优化

  1. @RequestBody 反序列化性能
    • 使用高效的 JSON 库(如 Jackson,默认且高效)。
    • 避免在 DTO 中包含不必要的大字段或深层嵌套对象。
    • 考虑使用 @JsonIgnore@JsonView 在反序列化时忽略不需要的字段。
  2. @RequestParam@PathVariable 解析:解析开销通常很低,但复杂的正则表达式(@PathVariable)可能有轻微影响。保持路径模式简单。
  3. 对象创建:频繁创建 DTO 实例是正常的。JVM 的新生代垃圾回收对此类短生命周期对象非常高效。
  4. 连接与线程:确保 Web 服务器配置(如 Tomcat 的 maxThreads)能处理预期的并发请求量,这比参数绑定本身的优化更重要。
  5. 缓存:对于基于 @RequestParam 的幂等 GET 请求,考虑使用 Spring Cache 或 HTTP 缓存减少后端计算。

总结

@RequestParam, @PathVariable, @RequestBody 是 Spring Boot 接收 HTTP 请求数据的三大支柱。熟练掌握它们的使用场景、属性配置和常见问题,是构建健壮 Web API 的基础。

快速掌握路径

  1. 动手编码:创建一个 @RestController,分别用这三个注解实现几个简单的 API 端点。
  2. 工具测试:使用 Postman、curl 或浏览器测试,观察不同请求方式(GET 查询参数 vs POST JSON 体)的效果。
  3. 调试:在方法内设置断点,观察参数是否被正确注入。
  4. 引入校验:为 @RequestBody DTO 添加 @NotNull, @Size 等注解,测试无效输入时的响应。
  5. 阅读文档:查阅 Spring Framework 官方文档中关于 "Annotation Support for Web MVC" 的章节。

掌握这些技巧,你就能高效、安全地处理各种前端或客户端传来的数据了。