RequestMapping
本章是 Spring MVC 请求映射体系的核心。DispatcherServlet 需要知道"哪个 URL 交给哪个方法处理",
@RequestMapping正是声明这种映射关系的元注解。@GetMapping、@PostMapping等所有方法级映射注解,本质上都是@RequestMapping的特化。理解@RequestMapping,就掌握了 Spring MVC 路由规则的完整表达能力。
定义与作用
@RequestMapping 是 Spring MVC 的核心映射注解,可以标注在类级和方法级。它的职责可以用一句话概括:声明控制器类或方法所处理的 HTTP 请求映射规则,包括 URL 路径、HTTP 方法、请求参数、请求头、消费/生产媒体类型等约束条件。
@RequestMapping 是所有 HTTP 方法注解的元注解:
// @GetMapping 的源码定义
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping { }
// @PostMapping 的源码定义
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping { }
// 同理:@PutMapping、@DeleteMapping、@PatchMapping
掌握 @RequestMapping,就等于掌握了所有映射注解的底层机制。
核心原理
类级 + 方法级路径拼接
拼接规则:
- 类级路径 + 方法级路径 = 完整请求路径
- 如果类级没有
@RequestMapping,方法级路径就是完整路径 - 路径拼接时自动处理斜杠,无需手动添加或删除
多维度匹配机制
图解:@RequestMapping 的匹配是多维度交集,所有条件同时满足才算匹配。这种精细控制使得同一个 URL 可以根据 HTTP 方法、请求头、Content-Type 等分发给不同的方法。
适用位置与常用属性
属性对比表
| 属性 | 类型 | 作用 | 示例 |
|---|---|---|---|
value / path | String[] | URL 路径映射 | "/employees"、"/{id}" |
method | RequestMethod[] | 约束 HTTP 方法 | RequestMethod.GET |
params | String[] | 约束请求参数 | "department"、"!archived" |
headers | String[] | 约束请求头 | "X-Api-Version=2" |
consumes | String[] | 约束请求 Content-Type | "application/json" |
produces | String[] | 约束响应 Accept / 设置响应 Content-Type | "application/json" |
Ant 风格 URL 模式
Spring MVC 支持 Ant 风格的路径模式,用于模糊匹配:
| 通配符 | 含义 | 示例 |
|---|---|---|
? | 匹配单个字符 | /employees/? 匹配 /employees/1 |
* | 匹配零个或多个字符(单层路径) | /employees/* 匹配 /employees/list,不匹配 /employees/1/detail |
** | 匹配零个或多个路径段(多层路径) | /employees/** 匹配 /employees/1/detail/address |
完整示例
场景
飞翔科技的员工管理系统需要精细控制请求路由。架构师白歌要求同一个基础路径 /employees 下,根据 HTTP 方法、请求参数、请求头的不同,分发给不同的处理方法。
控制器代码
package com.feixiang.web;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/employees")
public class EmployeeController {
// 容器已注入该 Bean,本教程不展开 Service 实现
@Autowired
private EmployeeService employeeService;
// 映射 1:GET /employees(无参数)
@RequestMapping(method = RequestMethod.GET)
public List<Employee> listAll() {
return employeeService.findAll();
}
// 映射 2:GET /employees?department=技术部
@RequestMapping(method = RequestMethod.GET, params = "department")
public List<Employee> listByDepartment(@RequestParam String department) {
return employeeService.findByDepartment(department);
}
// 映射 3:GET /employees/{id}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Employee getById(@PathVariable Long id) {
return employeeService.findById(id);
}
// 映射 4:POST /employees(只接受 JSON)
@RequestMapping(method = RequestMethod.POST,
consumes = "application/json",
produces = "application/json")
public Employee create(@RequestBody Employee employee) {
return employeeService.save(employee);
}
// 映射 5:GET /employees/export(要求请求头 X-Format=csv)
@RequestMapping(value = "/export", method = RequestMethod.GET,
headers = "X-Format=csv")
public String exportCsv() {
return employeeService.exportCsv();
}
}
映射规则汇总:
| 请求特征 | 匹配的方法 |
|---|---|
GET /employees | listAll() |
GET /employees?department=技术部 | listByDepartment() |
GET /employees/1001 | getById() |
POST /employees + Content-Type: application/json | create() |
GET /employees/export + X-Format: csv | exportCsv() |
HTTP 请求示例 1:带参数的分发
# 请求 A:无参数 → 调用 listAll()
curl -X GET http://localhost:8080/employees
# 请求 B:带 department 参数 → 调用 listByDepartment()
curl -X GET "http://localhost:8080/employees?department=技术部"
请求 A 响应:
[
{"id": 1001, "name": "张伟", "department": "技术部"},
{"id": 1002, "name": "李娜", "department": "市场部"},
{"id": 1003, "name": "王强", "department": "技术部"}
]
请求 B 响应:
[
{"id": 1001, "name": "张伟", "department": "技术部"},
{"id": 1003, "name": "王强", "department": "技术部"}
]
分析:
- 两个请求的 URL 都是
/employees,但params = "department"使它们分发给不同方法 listByDepartment()只返回技术部员工,因为department=技术部作为查询参数传入- 这是
@RequestMapping多维度匹配的典型应用
HTTP 请求示例 2:consumes 和 headers 约束
# 请求 C:正确的 POST(Content-Type 为 application/json)
curl -X POST http://localhost:8080/employees \
-H "Content-Type: application/json" \
-d '{"name":"赵敏","department":"财务部"}'
# 请求 D:错误的 POST(Content-Type 为 text/plain)
curl -X POST http://localhost:8080/employees \
-H "Content-Type: text/plain" \
-d 'name=赵敏'
请求 C 响应(201 Created):
{"id": 1004, "name": "赵敏", "department": "财务部"}
请求 D 响应(415 Unsupported Media Type):
{
"timestamp": "2024-06-15T10:30:00",
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type 'text/plain;charset=UTF-8' not supported"
}
分析:
consumes = "application/json"要求请求必须携带Content-Type: application/json- 请求 D 的
text/plain不匹配,Spring MVC 直接返回 415,不会进入 Controller 方法 - 这种前置约束避免了方法内部做 Content-Type 检查
易错场景与面试考点
误区一:类级和方法级路径拼接时重复写斜杠
错误写法:
@RequestMapping("/employees") // 类级以 / 开头
public class EmployeeController {
@RequestMapping("/list") // 方法级也以 / 开头
public String list() { }
}
// 实际映射路径:/employees/list(Spring 自动处理重复斜杠)
纠正:虽然 Spring 会自动处理重复斜杠,但推荐风格是类级以 / 开头,方法级不以 / 开头:
@RequestMapping("/employees")
public class EmployeeController {
@RequestMapping("list") // 推荐:方法级不加前导 /
public String list() { }
}
误区二:params 和 headers 的否定写法
错误认知:"params = "department" 表示请求可以有也可以没有 department 参数。"
纠正:params = "department" 表示必须有 department 参数。如果要表达"必须没有",使用 ! 前缀:
// 必须有 department 参数
@RequestMapping(params = "department")
// 必须没有 archived 参数
@RequestMapping(params = "!archived")
// 必须有 department=技术部
@RequestMapping(params = "department=技术部")
// 必须有 department,且值不是 离职
@RequestMapping(params = "department!=离职")
面试高频:@RequestMapping 的匹配维度
标准回答:
@RequestMapping可以从六个维度约束请求:path、method、params、headers、consumes、produces- 匹配是交集逻辑,所有条件同时满足才算匹配
- 类级和方法级的
value(path)会拼接为完整路径 consumes不匹配返回 415,produces不匹配返回 406,method不匹配返回 405- Spring 4.3+ 推荐使用
@GetMapping、@PostMapping等派生注解,语义更清晰
小结
@RequestMapping 是 Spring MVC 请求映射体系的元注解,它通过 path、method、params、headers、consumes、produces 六个维度精确控制请求路由。类级和方法级的路径自动拼接,Ant 风格通配符支持模糊匹配。它是 @GetMapping、@PostMapping 等所有派生注解的底层基础。
本章与全局的关系:本章讲解了 @RequestMapping 的完整能力。后续章节将分别讲解其派生注解 @GetMapping、@PostMapping、@PutMapping、@DeleteMapping,它们在语义上更精确,是实际开发中的首选。