一、核心概念

1. 请求映射(Request Mapping)

Spring MVC 使用注解将 HTTP 请求(URL、方法、参数、头信息等)映射到控制器(Controller)的处理方法上。核心注解是 @RequestMapping 及其衍生注解。

2. 核心注解

注解 说明 等效于
@RequestMapping 通用映射注解,可匹配所有 HTTP 方法 @RequestMapping(method = {RequestMethod.GET, ...})
@GetMapping 专门映射 HTTP GET 请求 @RequestMapping(method = RequestMethod.GET)
@PostMapping 专门映射 HTTP POST 请求 @RequestMapping(method = RequestMethod.POST)
@PutMapping 专门映射 HTTP PUT 请求 @RequestMapping(method = RequestMethod.PUT)
@DeleteMapping 专门映射 HTTP DELETE 请求 @RequestMapping(method = RequestMethod.DELETE)
@PatchMapping 专门映射 HTTP PATCH 请求 @RequestMapping(method = RequestMethod.PATCH)

3. 控制器(Controller)

负责处理特定请求并返回响应的组件。

注解 说明
@Controller 通用控制器注解。通常与 @ResponseBody 结合使用,或返回视图名(传统 MVC)。
@RestController 组合注解@Controller + @ResponseBody。其所有处理方法的返回值直接作为响应体(如 JSON、XML),无需 @ResponseBody

4. 映射属性

@RequestMapping 及其衍生注解的主要属性:

  • value / path: 指定 URL 路径(支持 Ant 风格和占位符)。
  • method: 指定 HTTP 方法(@GetMapping 等已隐含)。
  • params: 根据请求参数(存在、不存在、值)进行映射。
  • headers: 根据请求头信息进行映射。
  • consumes: 指定请求体(Content-Type)的媒体类型(如 application/json)。
  • produces: 指定响应体(Accept)的媒体类型(如 application/json),并设置响应头。

二、操作步骤(非常详细)

步骤 1:创建控制器类

1.1 使用 @RestController (推荐用于 REST API)

@RestController // 所有方法返回值直接作为响应体
@RequestMapping("/api/users") // 类级别的公共路径前缀
public class UserController {

    // 所有方法的路径都相对于 "/api/users"
}

1.2 使用 @Controller (传统 MVC 或混合场景)

@Controller // 方法返回值通常为视图名
@RequestMapping("/web")
public class WebController {

    @GetMapping("/home")
    public String home() {
        return "home"; // 返回视图名 "home",由视图解析器处理
    }

    @GetMapping("/data")
    @ResponseBody // 明确指示返回值作为响应体
    public Map<String, Object> data() {
        Map<String, Object> data = new HashMap<>();
        data.put("message", "Hello JSON");
        return data; // 返回 JSON
    }
}

步骤 2:定义处理方法(使用 @GetMapping, @PostMapping 等)

2.1 基本路径映射

@RestController
@RequestMapping("/api/users")
public class UserController {

    // GET /api/users - 获取所有用户
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }

    // GET /api/users/123 - 获取ID为123的用户
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id);
    }

    // POST /api/users - 创建新用户
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = userService.save(user);
        // 返回 201 Created 状态码和新资源的 Location 头
        return ResponseEntity.created(URI.create("/api/users/" + savedUser.getId())).body(savedUser);
    }

    // PUT /api/users/123 - 更新ID为123的用户
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id); // 确保 ID 一致
        return userService.update(user);
    }

    // DELETE /api/users/123 - 删除ID为123的用户
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
        return ResponseEntity.noContent().build(); // 204 No Content
    }
}

2.2 使用 @RequestMapping 的完整示例

@RestController
public class AdvancedController {

    // 同时映射 GET 和 POST 到 /data
    @RequestMapping(value = "/data", method = {RequestMethod.GET, RequestMethod.POST})
    public String handleData() {
        return "Data handled";
    }

    // 根据请求参数映射
    @GetMapping(value = "/search", params = "name")
    public String searchByName(@RequestParam String name) {
        return "Searching for: " + name;
    }

    @GetMapping(value = "/search", params = "email")
    public String searchByEmail(@RequestParam String email) {
        return "Searching for email: " + email;
    }

    // 根据请求头映射
    @GetMapping(value = "/admin", headers = "X-Auth-Token=secret123")
    public String adminOnly() {
        return "Admin access granted";
    }

    // 根据 Content-Type 映射
    @PostMapping(value = "/data", consumes = "application/json")
    public String handleJson(@RequestBody Map<String, Object> data) {
        return "Received JSON: " + data;
    }

    @PostMapping(value = "/data", consumes = "application/xml")
    public String handleXml(@RequestBody String xml) {
        return "Received XML: " + xml;
    }

    // 根据 Accept 映射(内容协商)
    @GetMapping(value = "/info", produces = "application/json")
    public Map<String, String> getInfoJson() {
        Map<String, String> info = new HashMap<>();
        info.put("format", "json");
        return info;
    }

    @GetMapping(value = "/info", produces = "text/plain")
    public String getInfoText() {
        return "format=text";
    }
}

步骤 3:路径变量与请求参数

3.1 @PathVariable

@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(@PathVariable Long userId, @PathVariable("orderId") Long id) {
    // /users/1/orders/100 -> userId=1, id=100
    return orderService.findByUserAndOrder(userId, id);
}

3.2 @RequestParam

@GetMapping("/users")
public List<User> getUsers(
    @RequestParam(required = false, defaultValue = "0") int page, // /users?page=1
    @RequestParam(required = false) String name, // /users?name=John
    @RequestParam List<String> roles // /users?roles=admin&roles=user
) {
    return userService.findByCriteria(page, name, roles);
}

步骤 4:请求体与响应

4.1 @RequestBody

@PostMapping("/users")
public User createUser(@RequestBody @Valid User user, BindingResult result) {
    if (result.hasErrors()) {
        throw new IllegalArgumentException("Invalid user data");
    }
    return userService.save(user);
}

4.2 ResponseEntity

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    Optional<User> user = userService.findById(id);
    return user.map(u -> ResponseEntity.ok(u))
               .orElse(ResponseEntity.notFound().build());
}

三、常见错误与解决方案

错误 原因 解决方案
404 Not Found URL 路径错误、控制器未被扫描、缺少 @RequestMapping 检查路径拼写、确保控制器在主类包或子包下、确认注解使用正确
405 Method Not Allowed 请求方法(GET/POST)与映射不匹配 检查 @GetMapping/@PostMapping 等注解,或使用 @RequestMapping(method=...)
415 Unsupported Media Type 请求体 Content-Type 不匹配 consumes 确保客户端发送的 Content-Type 头(如 application/json)与 @PostMapping(consumes="...") 一致
406 Not Acceptable 无法生成客户端 Accept 头要求的响应类型 检查 produces 属性和客户端 Accept 头,确保有匹配的 MessageConverter
MissingServletRequestParameterException 必需的 @RequestParam 未提供 检查 required=true 的参数是否在 URL 中,或设置 defaultValue
HttpMessageNotReadableException 请求体 JSON/XML 格式错误或无法反序列化 检查 JSON 结构、字段名、数据类型,确保与目标类匹配
同一路径多个方法冲突 多个 @RequestMapping 匹配同一请求 使用 params, headers, consumes, produces 进一步区分,或合并逻辑

四、注意事项

  1. @RestController vs @Controller
    • @RestController默认所有方法返回值为响应体。适用于 RESTful API。
    • @Controller默认返回值为视图名。需要 @ResponseBody 才能返回数据。适用于传统 MVC 或混合场景。
  2. 路径冲突:Spring 按最精确匹配原则选择方法。使用 consumes, produces, params, headers 解决歧义。
  3. @PathVariable@RequestParam
    • @PathVariable:从 URL 路径中提取值(/users/{id})。
    • @RequestParam:从 URL 查询参数中提取值(?name=John)。
  4. @RequestBody:通常用于 POST/PUT 请求体。一个方法只能有一个 @RequestBody
  5. @ValidBindingResult@Valid 后必须紧跟 BindingResult 参数来捕获验证错误,否则会抛出异常。
  6. 安全性:避免在路径变量中暴露敏感信息(如密码、密钥)。

五、使用技巧

  1. 类级别 @RequestMapping:为一组相关方法定义公共路径前缀,提高代码组织性。
  2. Ant 风格路径
    • ?:匹配单个字符。
    • *:匹配路径段内的零个或多个字符(不包含 /)。
    • **:匹配任意层次的路径。
    • 例:/users/*/info 匹配 /users/123/info 但不匹配 /users/123/details/info/users/** 匹配所有 /users 下的路径。
  3. 占位符{variableName}@PathVariable 中使用。
  4. produces 与内容协商:客户端可通过 Accept 头请求不同格式(JSON, XML),服务器根据 producesMessageConverter 返回。
  5. consumes 限制输入:确保 API 只接受预期格式的数据。
  6. paramsheaders 实现 API 版本控制
    @GetMapping(value = "/data", headers = "X-API-Version=1")
    public DataV1 getDataV1() { ... }
    
    @GetMapping(value = "/data", headers = "X-API-Version=2")
    public DataV2 getDataV2() { ... }
    
  7. @ResponseStatus:直接设置响应状态码。
    @PostMapping("/users")
    @ResponseStatus(HttpStatus.CREATED) // 201
    public User createUser(@RequestBody User user) { ... }
    

六、最佳实践

  1. 使用语义化的 HTTP 方法:GET(获取), POST(创建), PUT(全量更新), PATCH(部分更新), DELETE(删除)。
  2. 使用 @GetMapping, @PostMapping 等专用注解:比 @RequestMapping(method=...) 更清晰、简洁。
  3. 优先使用 @RestController:对于纯数据服务(REST API),它更直观。
  4. 设计清晰的 URL 结构:使用名词复数(/users)、层次结构(/users/{id}/orders)。
  5. 正确使用 HTTP 状态码
    • 200 OK:成功获取/更新资源。
    • 201 Created:成功创建资源(POST)。
    • 204 No Content:成功执行但无返回内容(DELETE)。
    • 400 Bad Request:客户端请求错误(参数、JSON 格式)。
    • 404 Not Found:资源不存在。
    • 405 Method Not Allowed:方法不被允许。
    • 500 Internal Server Error:服务器内部错误。
  6. 输入验证:使用 @Valid + Bean Validation (JSR-380) 进行参数校验。
  7. 异常处理:使用 @ControllerAdvice + @ExceptionHandler 统一处理异常,返回结构化错误信息。
  8. 文档化:使用 Swagger/OpenAPI (如 springdoc-openapi) 为 API 生成文档。

七、性能优化

  1. 避免复杂路径匹配:过多的 ** 或复杂的 Ant 模式可能影响路由性能。保持路径模式尽可能简单和具体。
  2. 谨慎使用 params/headers 映射:虽然强大,但增加路由决策复杂度。优先使用路径和 HTTP 方法区分。
  3. 对象映射性能
    • 选择高效的 MessageConverter(如 Jackson 对 JSON 的处理通常很快)。
    • 避免在 @RequestBody/@ResponseBody 对象中包含不必要的大字段或深层嵌套。
    • 考虑使用 @JsonIgnore@JsonView 控制序列化/反序列化范围。
  4. 连接与线程:确保 Web 服务器(如 Tomcat)的连接池和线程池配置合理,以处理并发请求。这通常在 application.yml 中配置。
  5. 缓存:对于幂等的 GET 请求,考虑使用 Spring Cache (@Cacheable) 或 HTTP 缓存头 (Cache-Control) 减少后端处理。

总结

@RequestMapping 及其衍生注解(@GetMapping, @PostMapping)和控制器注解(@Controller, @RestController)是构建 Spring Boot Web 应用的基石。它们共同定义了如何接收 HTTP 请求、调用业务逻辑以及返回响应。

快速掌握路径

  1. 动手实践:创建 @RestController,使用 @GetMapping, @PostMapping 定义几个 API。
  2. 测试:使用 curl, Postman 或浏览器测试 API,观察请求/响应。
  3. 理解区别:明确 @RestController@Controller 的不同,以及何时使用 @ResponseBody
  4. 学习参数绑定:掌握 @PathVariable, @RequestParam, @RequestBody 的用法。
  5. 处理异常:实现一个 @ControllerAdvice 来处理常见异常。

熟练运用这些概念,是开发高效、可靠 Web 服务的关键。