人人都会AI编程

12.2 控制器开发:请求映射注解、路径参数、查询参数、请求体

更新时间:2026-07-10

掌握了 Spring MVC 的整体运行流程之后,就可以开始编写真正处理 HTTP 请求的控制器了。控制器是 MVC 中承上启下的核心组件:它接收前端请求,调用业务层处理,再决定返回什么内容给客户端。Spring 为此提供了一套丰富且直观的注解,能够将 HTTP 请求的各个部分——URL 路径、查询字符串、请求体——清晰地映射到 Java 方法的参数上。

12.2.1 请求映射:从 @RequestMapping 到快捷注解

所有控制器类都必须被 Spring 管理,标注 @Controller@RestController。两者的区别在于:

  • @Controller:用于传统的视图渲染,方法通常返回视图名(如 "orderList"),配合模板引擎使用。
  • @RestController:等于 @Controller + @ResponseBody,每个方法的返回值直接写入 HTTP 响应体,通常序列化为 JSON。开发 RESTful API 时,统一使用 @RestController

在类和方法上使用 @RequestMapping 可以指定该方法处理的请求路径和 HTTP 方法。但是,直接使用 @RequestMapping 需要额外指定 method 属性,略显繁琐。Spring 提供了更语义化的快捷注解:

  • @GetMapping:处理 GET 请求
  • @PostMapping:处理 POST 请求
  • @PutMapping:处理 PUT 请求
  • @DeleteMapping:处理 DELETE 请求
  • @PatchMapping:处理 PATCH 请求

这些注解其实就是 @RequestMapping 的特定方法版本,阅读代码时一目了然。

下面是一个最简单的控制器示例,展示如何使用 @GetMapping 处理根路径的请求:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, Spring MVC!";
    }
}

访问 http://localhost:8080/hello,页面会直接显示字符串 "Hello, Spring MVC!"。如果你的项目引入了 Jackson(通常通过 spring-boot-starter-web 自动引入),返回对象会被自动转换为 JSON:

@GetMapping("/user")
public User getUser() {
    return new User("张三", "zhangsan@example.com");
}

当方法返回 User 对象时,响应体会是 {"name":"张三","email":"zhangsan@example.com"},Content-Type 自动设为 application/json

12.2.2 路径参数:@PathVariable

RESTful 风格的 API 经常会在 URL 路径中携带标识符,例如 /users/123 表示获取 id 为 123 的用户。Spring 使用 @PathVariable 将 URL 模板中的变量绑定到方法参数。

首先,在 @GetMapping 的路径中用 {变量名} 声明占位符,然后在方法参数中使用 @PathVariable 接收:

@GetMapping("/users/{id}")
public User getUserById(@PathVariable("id") Long userId) {
    // 根据 userId 查询数据库
    return userService.findById(userId);
}

如果方法参数名与路径变量名一致,可以省略 @PathVariable 的 value 属性:

@GetMapping("/users/{id}")
public User getUserById(@PathVariable Long id) {
    // ...
}

多个路径参数同样直观:

@GetMapping("/orders/{orderId}/items/{itemId}")
public OrderItem getItem(@PathVariable Long orderId, 
                         @PathVariable Long itemId) {
    return orderService.findItem(orderId, itemId);
}

在真实项目中,路径参数常用于查询或删除单个资源。务必在 Service 层做好空值处理(如找不到返回 404),避免抛出不可控的异常。

12.2.3 查询参数:@RequestParam

当客户端通过 URL 的查询字符串传递参数(如 /users?page=1&size=10)时,使用 @RequestParam 将其绑定到控制器方法参数。

@GetMapping("/users")
public List<User> listUsers(@RequestParam int page, 
                            @RequestParam int size) {
    // page = 1, size = 10
    return userService.list(page, size);
}

@RequestParam 的几个重要属性必须掌握:

  • value / name:指定查询参数名,如果方法参数名与查询参数名一致可省略。
  • required:是否必传,默认 true。如果设置为 false,参数缺失时值为 null(或基本类型的默认值)。
  • defaultValue:指定默认值,当参数缺失时使用。注意:设定了 defaultValue 后,required 自动变为 false
@GetMapping("/search")
public List<Product> search(@RequestParam String keyword,
                            @RequestParam(defaultValue = "0") int minPrice,
                            @RequestParam(required = false) String category) {
    // keyword 必填,minPrice 默认0,category 可选
    return productService.search(keyword, minPrice, category);
}

常见误区: 不要将查询参数用于传递复杂的 JSON 数据。当参数过多或结构复杂时,应该改用 POST 请求并将数据放入请求体。查询参数适合扁平、少量的筛选条件。

12.2.4 请求体:@RequestBody

对于 POST、PUT、PATCH 等请求,客户端通常会将数据放在 HTTP 请求体中,格式多为 JSON。@RequestBody 注解负责将请求体的内容反序列化为 Java 对象。

@PostMapping("/users")
public User createUser(@RequestBody User user) {
    return userService.save(user);
}

当客户端发送如下 JSON:

{
    "name": "李四",
    "email": "lisi@example.com"
}

Spring 会通过 HttpMessageConverter(默认使用 Jackson)将 JSON 映射为 User 对象,再传入方法。整个过程是自动的,但前提是 Java 对象的属性名与 JSON 的字段名相匹配(或使用 @JsonProperty 指定映射)。

与校验配合使用

真实项目中,绝不会信任客户端传入的裸数据,必须进行校验。将 @Valid@Validated 注解加在 @RequestBody 的参数前即可触发 JSR-303 校验:

@PostMapping("/users")
public User createUser(@Valid @RequestBody User user) {
    return userService.save(user);
}

在实体类中声明校验规则:

public class User {
    @NotBlank(message = "姓名不能为空")
    private String name;

    @Email(message = "邮箱格式不正确")
    private String email;
    // getters & setters
}

当校验失败时,Spring 会抛出 MethodArgumentNotValidException,我们可以通过全局异常处理器(@ControllerAdvice)统一返回友好的错误信息。

注意事项:

  1. 一个方法只能有一个 @RequestBody 参数,因为一个 HTTP 请求只有一个请求体。
  2. 不要将 @RequestBody 和表单相关的 @ModelAttribute 混用,保持设计的一致性。
  3. 对于文件上传,请求体的处理会有所不同,应该使用 @RequestParam MultipartFile,而不是 @RequestBody

12.2.5 组合实战:一个典型的 CRUD 控制器

将上述注解组合在一起,基本上就能完成一个标准资源的增删改查操作。下面以“商品管理”为例,展示一个完整的 RESTful 控制器片段:

@RestController
@RequestMapping("/products")  // 类级别统一路径前缀
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    // 分页查询商品列表,支持查询参数
    @GetMapping
    public List<Product> list(@RequestParam(defaultValue = "1") int page,
                              @RequestParam(defaultValue = "20") int size) {
        return productService.list(page, size);
    }

    // 根据 ID 查询单个商品
    @GetMapping("/{id}")
    public Product getById(@PathVariable Long id) {
        return productService.findById(id);
    }

    // 新增商品,请求体携带 JSON 数据
    @PostMapping
    public Product create(@Valid @RequestBody Product product) {
        return productService.save(product);
    }

    // 全量更新商品
    @PutMapping("/{id}")
    public Product update(@PathVariable Long id, 
                          @Valid @RequestBody Product product) {
        product.setId(id);
        return productService.update(product);
    }

    // 删除商品
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        productService.delete(id);
    }
}

这个控制器类覆盖了日常开发中最常见的场景:路径参数用于资源定位,查询参数用于列表筛选,请求体承载复杂的数据结构。所有方法都专注于 HTTP 层面的协调,真正的业务逻辑下沉到 Service 层,控制器的代码因而干净、可测试。

在实际项目中,为了保持代码的整洁和统一,往往还会封装一个通用的响应对象(如 Result<T>)包含状态码、消息和数据,避免在方法中直接返回裸数据。不过,响应封装的优化更适合在“统一响应结构”一节中深入探讨,这里先掌握核心的参数绑定技能即可。