Appearance
Spring MVC 请求映射详解:从入门到精通 🎯
概述
在 Spring MVC 中,请求映射(Request Mapping)是连接 HTTP 请求与控制器方法的桥梁。它决定了哪个 URL 请求会被哪个控制器方法处理。掌握请求映射是构建 RESTful API 的基础技能。
IMPORTANT
请求映射的核心作用是建立 URL 路径与处理方法之间的对应关系,让 Spring MVC 知道如何路由请求。
为什么需要请求映射? 🤔
想象一下,如果没有请求映射机制:
kotlin
// 传统方式:需要手动解析 URL 和 HTTP 方法
class PersonServlet : HttpServlet() {
override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
val pathInfo = request.pathInfo
when {
pathInfo?.matches(Regex("/\\d+")) == true -> {
// 处理获取单个用户的逻辑
val id = pathInfo.substring(1).toLong()
getPerson(id, response)
}
pathInfo == "/" -> {
// 处理获取所有用户的逻辑
getAllPersons(response)
}
else -> {
response.sendError(404)
}
}
}
override fun doPost(request: HttpServletRequest, response: HttpServletResponse) {
// 手动处理 POST 请求...
}
}
kotlin
// Spring MVC 方式:声明式映射,简洁明了
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
fun getPerson(@PathVariable id: Long): Person {
// 只需关注业务逻辑
return personService.findById(id)
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun createPerson(@RequestBody person: Person) {
personService.save(person)
}
}
请求映射解决的核心问题:
- 🎯 路由复杂性:自动处理 URL 路径匹配和参数提取
- 🔄 HTTP 方法区分:根据 GET、POST、PUT、DELETE 等方法自动分发
- 📝 参数绑定:自动将 URL 参数、请求体等绑定到方法参数
- 🎨 内容协商:根据请求头自动选择合适的处理方法
@RequestMapping:万能的映射注解
@RequestMapping
是 Spring MVC 中最基础的请求映射注解,它可以用在类级别和方法级别。
基本用法
kotlin
@RestController
@RequestMapping("/api/v1/persons")
class PersonController {
@RequestMapping(
value = ["/{id}"], // URL 路径
method = [RequestMethod.GET], // HTTP 方法
produces = ["application/json"] // 响应内容类型
)
fun getPerson(@PathVariable id: Long): Person {
return personService.findById(id)
}
}
类级别与方法级别组合
kotlin
@RestController
@RequestMapping("/api/v1/users")
class UserController {
@RequestMapping("/profile")
fun getUserProfile(): UserProfile {
// 实际访问路径:/api/v1/users/profile
return userService.getCurrentUserProfile()
}
@RequestMapping("/{userId}/orders")
fun getUserOrders(@PathVariable userId: Long): List<Order> {
// 实际访问路径:/api/v1/users/{userId}/orders
return orderService.findByUserId(userId)
}
}
WARNING
同一个元素(类、接口或方法)上不能同时使用多个 @RequestMapping
注解,否则只有第一个会生效。
HTTP 方法专用注解:更简洁的选择 ✨
Spring 提供了针对特定 HTTP 方法的快捷注解,让代码更加语义化:
kotlin
@RestController
@RequestMapping("/api/books")
class BookController {
@GetMapping
fun getAllBooks(): List<Book> {
return bookService.findAll()
}
@GetMapping("/{id}")
fun getBook(@PathVariable id: Long): Book {
return bookService.findById(id)
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun createBook(@RequestBody book: Book): Book {
return bookService.save(book)
}
@PutMapping("/{id}")
fun updateBook(@PathVariable id: Long, @RequestBody book: Book): Book {
return bookService.update(id, book)
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteBook(@PathVariable id: Long) {
bookService.deleteById(id)
}
@PatchMapping("/{id}/status")
fun updateBookStatus(@PathVariable id: Long, @RequestParam status: String) {
bookService.updateStatus(id, status)
}
}
TIP
推荐使用具体的 HTTP 方法注解(@GetMapping
、@PostMapping
等)而不是通用的 @RequestMapping
,这样代码更清晰,意图更明确。
URI 模式匹配:灵活的路径设计 🛣️
基本通配符
kotlin
@RestController
class FileController {
@GetMapping("/files/ima?e.png")
fun getSingleCharWildcard(): String {
// 匹配:/files/image.png, /files/imabe.png
// 不匹配:/files/imaaae.png
return "Single character wildcard"
}
@GetMapping("/files/*.png")
fun getMultiCharWildcard(): String {
// 匹配:/files/photo.png, /files/avatar.png
// 不匹配:/files/docs/photo.png
return "Multiple characters in single segment"
}
@GetMapping("/static/**")
fun getMultiSegmentWildcard(): String {
// 匹配:/static/css/style.css, /static/js/app.js
// 匹配:/static/images/icons/user.png
return "Multiple path segments"
}
}
路径变量捕获
kotlin
@RestController
@RequestMapping("/api")
class PathVariableController {
@GetMapping("/users/{userId}/posts/{postId}")
fun getUserPost(
@PathVariable userId: Long,
@PathVariable postId: Long
): Post {
return postService.findByUserAndId(userId, postId)
}
// 使用正则表达式约束路径变量
@GetMapping("/products/{productCode:[A-Z]{2}\\d{4}}")
fun getProductByCode(@PathVariable productCode: String): Product {
// 只匹配格式如 AB1234 的产品代码
return productService.findByCode(productCode)
}
// 复杂的路径变量组合
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun downloadFile(
@PathVariable name: String,
@PathVariable version: String,
@PathVariable ext: String
): ResponseEntity<ByteArray> {
// 匹配:/spring-boot-3.2.1.jar
return fileService.downloadFile(name, version, ext)
}
}
路径变量的高级用法
kotlin
@RestController
class AdvancedPathController {
// 可选路径变量
@GetMapping(["/search", "/search/{category}"])
fun search(@PathVariable(required = false) category: String?): List<Product> {
return if (category != null) {
productService.findByCategory(category)
} else {
productService.findAll()
}
}
// 捕获剩余路径
@GetMapping("/files/{*path}")
fun getFile(@PathVariable path: String): ResponseEntity<ByteArray> {
// path 可以包含多个路径段,如:documents/2024/report.pdf
return fileService.getFile(path)
}
}
内容类型协商:精确的请求处理 📋
消费内容类型(consumes)
kotlin
@RestController
@RequestMapping("/api/data")
class ContentController {
@PostMapping(consumes = ["application/json"])
fun handleJson(@RequestBody data: JsonData): String {
return "Processed JSON data"
}
@PostMapping(consumes = ["application/xml"])
fun handleXml(@RequestBody data: XmlData): String {
return "Processed XML data"
}
@PostMapping(consumes = ["multipart/form-data"])
fun handleFileUpload(@RequestParam file: MultipartFile): String {
return "File uploaded: ${file.originalFilename}"
}
// 排除特定内容类型
@PostMapping(consumes = ["!text/plain"])
fun handleNonPlainText(@RequestBody data: Any): String {
return "Accepts any content type except plain text"
}
}
生产内容类型(produces)
kotlin
@RestController
@RequestMapping("/api/content")
class ResponseContentController {
@GetMapping(value = ["/user/{id}"], produces = ["application/json"])
fun getUserAsJson(@PathVariable id: Long): User {
return userService.findById(id)
}
@GetMapping(value = ["/user/{id}"], produces = ["application/xml"])
fun getUserAsXml(@PathVariable id: Long): User {
return userService.findById(id)
}
@GetMapping(value = ["/report"], produces = ["application/pdf"])
fun generatePdfReport(): ResponseEntity<ByteArray> {
val pdfData = reportService.generatePdf()
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=report.pdf")
.body(pdfData)
}
// 支持多种响应格式
@GetMapping(value = ["/data"], produces = ["application/json", "application/xml"])
fun getData(): DataResponse {
// Spring 会根据客户端的 Accept 头选择合适的格式
return dataService.getData()
}
}
参数和头部条件:精细化控制 🎛️
请求参数条件
kotlin
@RestController
@RequestMapping("/api/search")
class SearchController {
// 要求特定参数存在
@GetMapping(params = ["type"])
fun searchWithType(@RequestParam type: String): List<SearchResult> {
return searchService.searchByType(type)
}
// 要求参数具有特定值
@GetMapping(params = ["type=advanced"])
fun advancedSearch(@RequestParam type: String): List<SearchResult> {
return searchService.advancedSearch()
}
// 要求参数不存在
@GetMapping(params = ["!debug"])
fun normalSearch(): List<SearchResult> {
return searchService.normalSearch()
}
// 组合条件
@GetMapping(params = ["type=premium", "version"])
fun premiumSearch(
@RequestParam type: String,
@RequestParam version: String
): List<SearchResult> {
return searchService.premiumSearch(version)
}
}
请求头条件
kotlin
@RestController
@RequestMapping("/api/mobile")
class MobileController {
// 要求特定请求头
@GetMapping(value = ["/data"], headers = ["X-API-Version"])
fun getDataWithVersion(@RequestHeader("X-API-Version") version: String): ApiResponse {
return apiService.getDataForVersion(version)
}
// 要求请求头具有特定值
@GetMapping(value = ["/premium"], headers = ["X-User-Type=premium"])
fun getPremiumContent(): PremiumContent {
return contentService.getPremiumContent()
}
// 移动端专用接口
@GetMapping(value = ["/app-data"], headers = ["User-Agent=*Mobile*"])
fun getMobileAppData(): MobileAppData {
return mobileService.getAppData()
}
}
实际业务场景应用 💼
电商 API 设计示例
kotlin
@RestController
@RequestMapping("/api/v1/ecommerce")
class EcommerceController {
// 商品管理
@GetMapping("/products")
fun getProducts(
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "20") size: Int,
@RequestParam(required = false) category: String?
): Page<Product> {
return productService.findProducts(page, size, category)
}
@GetMapping("/products/{productId}")
fun getProduct(@PathVariable productId: Long): Product {
return productService.findById(productId)
}
// 购物车操作
@PostMapping("/cart/items")
@ResponseStatus(HttpStatus.CREATED)
fun addToCart(@RequestBody cartItem: CartItem): CartItem {
return cartService.addItem(cartItem)
}
@PutMapping("/cart/items/{itemId}")
fun updateCartItem(
@PathVariable itemId: Long,
@RequestBody updateRequest: CartItemUpdateRequest
): CartItem {
return cartService.updateItem(itemId, updateRequest)
}
// 订单处理
@PostMapping(
value = ["/orders"],
consumes = ["application/json"],
produces = ["application/json"]
)
@ResponseStatus(HttpStatus.CREATED)
fun createOrder(@RequestBody orderRequest: OrderRequest): Order {
return orderService.createOrder(orderRequest)
}
@GetMapping("/orders/{orderId}/status")
fun getOrderStatus(@PathVariable orderId: Long): OrderStatus {
return orderService.getOrderStatus(orderId)
}
}
API 版本控制策略
kotlin
// 通过路径版本控制
@RestController
@RequestMapping("/api/v1/users")
class UserV1Controller {
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): UserV1Response {
return userService.getUserV1(id)
}
}
@RestController
@RequestMapping("/api/v2/users")
class UserV2Controller {
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): UserV2Response {
return userService.getUserV2(id)
}
}
// 通过请求头版本控制
@RestController
@RequestMapping("/api/users")
class UserController {
@GetMapping(value = ["/{id}"], headers = ["API-Version=1.0"])
fun getUserV1(@PathVariable id: Long): UserV1Response {
return userService.getUserV1(id)
}
@GetMapping(value = ["/{id}"], headers = ["API-Version=2.0"])
fun getUserV2(@PathVariable id: Long): UserV2Response {
return userService.getUserV2(id)
}
}
请求映射的执行流程 🔄
自定义注解:提升开发效率 🚀
kotlin
// 自定义组合注解
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@GetMapping
@ResponseBody
annotation class ApiGet(
val value: String = "",
val produces: Array<String> = ["application/json"]
)
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@PostMapping
@ResponseBody
@ResponseStatus(HttpStatus.CREATED)
annotation class ApiPost(
val value: String = "",
val consumes: Array<String> = ["application/json"],
val produces: Array<String> = ["application/json"]
)
// 使用自定义注解
@RestController
@RequestMapping("/api/custom")
class CustomAnnotationController {
@ApiGet("/users/{id}")
fun getUser(@PathVariable id: Long): User {
return userService.findById(id)
}
@ApiPost("/users")
fun createUser(@RequestBody user: User): User {
return userService.save(user)
}
}
最佳实践与注意事项 ⚡
1. URL 设计原则
kotlin
@RestController
class BestPracticeController {
// ✅ 好的设计:RESTful 风格
@GetMapping("/api/v1/users/{userId}/orders")
fun getUserOrders(@PathVariable userId: Long): List<Order> {
return orderService.findByUserId(userId)
}
// ❌ 避免:动词式 URL
// @GetMapping("/api/v1/getUserOrders/{userId}")
// ✅ 好的设计:资源层次清晰
@GetMapping("/api/v1/departments/{deptId}/employees/{empId}")
fun getDepartmentEmployee(
@PathVariable deptId: Long,
@PathVariable empId: Long
): Employee {
return employeeService.findByDepartmentAndId(deptId, empId)
}
}
2. 异常处理策略
kotlin
@RestController
@RequestMapping("/api/products")
class ProductController {
@GetMapping("/{id}")
fun getProduct(@PathVariable id: Long): Product {
return productService.findById(id)
?: throw ProductNotFoundException("Product not found: $id")
}
}
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException::class)
@ResponseStatus(HttpStatus.NOT_FOUND)
fun handleProductNotFound(ex: ProductNotFoundException): ErrorResponse {
return ErrorResponse("PRODUCT_NOT_FOUND", ex.message)
}
}
3. 性能优化建议
TIP
路径匹配优化建议
- 从 Spring 6.0 开始,默认使用
PathPattern
替代AntPathMatcher
,性能更好 - 避免过于复杂的正则表达式模式
- 将更具体的映射放在前面,减少匹配时间
kotlin
@RestController
class OptimizedController {
// ✅ 具体路径优先
@GetMapping("/api/users/current")
fun getCurrentUser(): User {
return userService.getCurrentUser()
}
@GetMapping("/api/users/{id}")
fun getUser(@PathVariable id: Long): User {
return userService.findById(id)
}
// ✅ 使用简单的路径变量模式
@GetMapping("/api/orders/{orderId:[0-9]+}")
fun getOrder(@PathVariable orderId: Long): Order {
return orderService.findById(orderId)
}
}
总结 📝
Spring MVC 的请求映射机制为我们提供了强大而灵活的 URL 路由能力:
- 🎯 @RequestMapping 是基础,提供了完整的映射配置选项
- ✨ HTTP 方法专用注解 让代码更简洁、语义更清晰
- 🛣️ URI 模式匹配 支持通配符、路径变量和正则表达式
- 📋 内容协商 实现了精确的请求处理和响应格式控制
- 🎛️ 条件匹配 提供了参数和头部的精细化控制
掌握这些特性,你就能设计出优雅、高效的 RESTful API,为用户提供更好的服务体验! 🚀
NOTE
请求映射不仅仅是技术实现,更是 API 设计哲学的体现。好的映射设计能让 API 更直观、更易用、更易维护。