Skip to content

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 更直观、更易用、更易维护。