一、核心概念
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 进一步区分,或合并逻辑 |
四、注意事项
@RestController
vs@Controller
:@RestController
:默认所有方法返回值为响应体。适用于 RESTful API。@Controller
:默认返回值为视图名。需要@ResponseBody
才能返回数据。适用于传统 MVC 或混合场景。
- 路径冲突:Spring 按最精确匹配原则选择方法。使用
consumes
,produces
,params
,headers
解决歧义。 @PathVariable
与@RequestParam
:@PathVariable
:从 URL 路径中提取值(/users/{id}
)。@RequestParam
:从 URL 查询参数中提取值(?name=John
)。
@RequestBody
:通常用于 POST/PUT 请求体。一个方法只能有一个@RequestBody
。@Valid
和BindingResult
:@Valid
后必须紧跟BindingResult
参数来捕获验证错误,否则会抛出异常。- 安全性:避免在路径变量中暴露敏感信息(如密码、密钥)。
五、使用技巧
- 类级别
@RequestMapping
:为一组相关方法定义公共路径前缀,提高代码组织性。 - Ant 风格路径:
?
:匹配单个字符。*
:匹配路径段内的零个或多个字符(不包含/
)。**
:匹配任意层次的路径。- 例:
/users/*/info
匹配/users/123/info
但不匹配/users/123/details/info
;/users/**
匹配所有/users
下的路径。
- 占位符:
{variableName}
在@PathVariable
中使用。 produces
与内容协商:客户端可通过Accept
头请求不同格式(JSON, XML),服务器根据produces
和MessageConverter
返回。consumes
限制输入:确保 API 只接受预期格式的数据。params
和headers
实现 API 版本控制:@GetMapping(value = "/data", headers = "X-API-Version=1") public DataV1 getDataV1() { ... } @GetMapping(value = "/data", headers = "X-API-Version=2") public DataV2 getDataV2() { ... }
@ResponseStatus
:直接设置响应状态码。@PostMapping("/users") @ResponseStatus(HttpStatus.CREATED) // 201 public User createUser(@RequestBody User user) { ... }
六、最佳实践
- 使用语义化的 HTTP 方法:GET(获取), POST(创建), PUT(全量更新), PATCH(部分更新), DELETE(删除)。
- 使用
@GetMapping
,@PostMapping
等专用注解:比@RequestMapping(method=...)
更清晰、简洁。 - 优先使用
@RestController
:对于纯数据服务(REST API),它更直观。 - 设计清晰的 URL 结构:使用名词复数(
/users
)、层次结构(/users/{id}/orders
)。 - 正确使用 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:服务器内部错误。
- 输入验证:使用
@Valid
+ Bean Validation (JSR-380) 进行参数校验。 - 异常处理:使用
@ControllerAdvice
+@ExceptionHandler
统一处理异常,返回结构化错误信息。 - 文档化:使用 Swagger/OpenAPI (如
springdoc-openapi
) 为 API 生成文档。
七、性能优化
- 避免复杂路径匹配:过多的
**
或复杂的 Ant 模式可能影响路由性能。保持路径模式尽可能简单和具体。 - 谨慎使用
params
/headers
映射:虽然强大,但增加路由决策复杂度。优先使用路径和 HTTP 方法区分。 - 对象映射性能:
- 选择高效的
MessageConverter
(如 Jackson 对 JSON 的处理通常很快)。 - 避免在
@RequestBody
/@ResponseBody
对象中包含不必要的大字段或深层嵌套。 - 考虑使用
@JsonIgnore
或@JsonView
控制序列化/反序列化范围。
- 选择高效的
- 连接与线程:确保 Web 服务器(如 Tomcat)的连接池和线程池配置合理,以处理并发请求。这通常在
application.yml
中配置。 - 缓存:对于幂等的 GET 请求,考虑使用 Spring Cache (
@Cacheable
) 或 HTTP 缓存头 (Cache-Control
) 减少后端处理。
总结
@RequestMapping
及其衍生注解(@GetMapping
, @PostMapping
)和控制器注解(@Controller
, @RestController
)是构建 Spring Boot Web 应用的基石。它们共同定义了如何接收 HTTP 请求、调用业务逻辑以及返回响应。
快速掌握路径:
- 动手实践:创建
@RestController
,使用@GetMapping
,@PostMapping
定义几个 API。 - 测试:使用
curl
, Postman 或浏览器测试 API,观察请求/响应。 - 理解区别:明确
@RestController
和@Controller
的不同,以及何时使用@ResponseBody
。 - 学习参数绑定:掌握
@PathVariable
,@RequestParam
,@RequestBody
的用法。 - 处理异常:实现一个
@ControllerAdvice
来处理常见异常。
熟练运用这些概念,是开发高效、可靠 Web 服务的关键。