Skip to content

Spring MVC Matrix Variables 深度解析 🎯

什么是 Matrix Variables?

Matrix Variables(矩阵变量)是 Spring MVC 中一个相对冷门但非常实用的特性。它允许我们在 URL 的路径段中传递键值对参数,这些参数以分号分隔,为 RESTful API 设计提供了更灵活的参数传递方式。

NOTE

Matrix Variables 基于 RFC 3986 规范中关于路径段中名值对的定义,Tim Berners-Lee 在一篇经典文章中将其称为"矩阵变量",它们也可以被称为 URI 路径参数。

为什么需要 Matrix Variables? 🤔

在传统的 Web 开发中,我们通常使用查询参数(Query Parameters)来传递额外信息:

/api/cars?color=red&year=2012

但在某些场景下,Matrix Variables 提供了更优雅的解决方案:

kotlin
// URL: /api/cars?color=red&year=2012
@GetMapping("/api/cars")
fun getCars(
    @RequestParam color: String,
    @RequestParam year: Int
): List<Car> {
    // 查询参数在 URL 末尾,语义不够清晰
    return carService.findByColorAndYear(color, year)
}
kotlin
// URL: /api/cars;color=red;year=2012
@GetMapping("/api/cars")
fun getCars(
    @MatrixVariable color: String,
    @MatrixVariable year: Int
): List<Car> {
    // 参数直接嵌入路径段,语义更清晰
    return carService.findByColorAndYear(color, year)
}

TIP

Matrix Variables 特别适用于需要对特定资源进行细粒度过滤或配置的场景,它们让 URL 更具语义化,参数与资源的关系更加明确。

Matrix Variables 的语法规则 📝

Matrix Variables 遵循以下语法规则:

  • 分号分隔:多个变量用分号 ; 分隔
  • 逗号分隔多值:同一变量的多个值用逗号 , 分隔
  • 重复变量名:也可以通过重复变量名来指定多个值

基础用法示例 🚀

1. 简单的 Matrix Variable

kotlin
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
fun findPet(
    @PathVariable petId: String,
    @MatrixVariable q: Int
) {
    // petId == "42"
    // q == 11
    println("查找宠物 ID: $petId, 查询参数: $q")
}

IMPORTANT

注意 URL 模式必须使用 URI 变量 {petId} 来匹配包含 matrix variables 的路径段,这样可以确保请求能够成功匹配,不受 matrix variables 顺序和存在性的影响。

2. 多路径段中的 Matrix Variables

当 URL 包含多个路径段,且每个段都有 matrix variables 时,需要明确指定变量所属的路径段:

kotlin
// GET /owners/42;q=11/pets/21;q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
    @MatrixVariable(name = "q", pathVar = "ownerId") q1: Int, 
    @MatrixVariable(name = "q", pathVar = "petId") q2: Int
) {
    // q1 == 11 (来自 ownerId 路径段)
    // q2 == 22 (来自 petId 路径段)
    println("主人查询参数: $q1, 宠物查询参数: $q2")
}

3. 可选参数和默认值

kotlin
// GET /pets/42 (没有 matrix variables)
@GetMapping("/pets/{petId}")
fun findPet(
    @MatrixVariable(required = false, defaultValue = "1") q: Int
) {
    // q == 1 (使用默认值)
    println("查询参数: $q")
}

4. 获取所有 Matrix Variables

使用 MultiValueMap 可以获取所有的 matrix variables:

kotlin
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
    @MatrixVariable matrixVars: MultiValueMap<String, String>, 
    @MatrixVariable(pathVar = "petId") petMatrixVars: MultiValueMap<String, String> 
) {
    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
    
    println("所有矩阵变量: $matrixVars")
    println("宠物路径段的矩阵变量: $petMatrixVars")
}

实际业务场景应用 💼

场景1:商品筛选API

kotlin
@RestController
@RequestMapping("/api/products")
class ProductController {

    // GET /api/products/electronics;brand=apple,samsung;price=1000-2000;inStock=true
    @GetMapping("/{category}")
    fun getProducts(
        @PathVariable category: String,
        @MatrixVariable(required = false) brand: List<String>?, 
        @MatrixVariable(required = false) price: String?, 
        @MatrixVariable(required = false, defaultValue = "false") inStock: Boolean
    ): ResponseEntity<List<Product>> {
        
        val filters = ProductFilter(
            category = category,
            brands = brand ?: emptyList(),
            priceRange = price?.let { parsePriceRange(it) },
            inStockOnly = inStock
        )
        
        val products = productService.findProducts(filters)
        return ResponseEntity.ok(products)
    }
    
    private fun parsePriceRange(priceStr: String): PriceRange? {
        // 解析价格区间 "1000-2000"
        val parts = priceStr.split("-")
        return if (parts.size == 2) {
            PriceRange(parts[0].toInt(), parts[1].toInt())
        } else null
    }
}

场景2:文件版本管理

kotlin
@RestController
@RequestMapping("/api/files")
class FileController {

    // GET /api/files/document.pdf;version=1.2;format=compressed;access=readonly
    @GetMapping("/{filename}")
    fun getFile(
        @PathVariable filename: String,
        @MatrixVariable(required = false, defaultValue = "latest") version: String, 
        @MatrixVariable(required = false, defaultValue = "original") format: String, 
        @MatrixVariable(required = false, defaultValue = "readwrite") access: String
    ): ResponseEntity<ByteArray> {
        
        val fileRequest = FileRequest(
            filename = filename,
            version = version,
            format = FileFormat.valueOf(format.uppercase()),
            accessMode = AccessMode.valueOf(access.uppercase())
        )
        
        val fileData = fileService.getFile(fileRequest)
        return ResponseEntity.ok(fileData)
    }
}

配置启用 Matrix Variables ⚙️

WARNING

Matrix Variables 默认是禁用的!你需要显式启用它们。

Java Configuration 方式

kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
    
    override fun configurePathMatch(configurer: PathMatchConfigurer) {
        val urlPathHelper = UrlPathHelper()
        urlPathHelper.setRemoveSemicolonContent(false) 
        configurer.setUrlPathHelper(urlPathHelper)
    }
}

XML Configuration 方式

xml
<mvc:annotation-driven enable-matrix-variables="true"/>

Matrix Variables vs Query Parameters 对比 ⚖️

特性Matrix VariablesQuery Parameters
位置嵌入在路径段中URL 末尾
语法/path;key=value/path?key=value
语义与特定资源强关联通用查询条件
缓存友好更友好(路径的一部分)一般
SEO友好更友好一般
复杂度需要额外配置开箱即用

最佳实践建议 ✅

使用建议

  1. 资源特定参数:当参数与特定资源密切相关时,使用 Matrix Variables
  2. 版本控制:API 版本信息可以通过 Matrix Variables 传递
  3. 过滤条件:对资源进行细粒度过滤时特别有用
  4. 配置参数:资源的展示格式、访问模式等配置

注意事项

  • Matrix Variables 不如 Query Parameters 常见,团队成员可能不熟悉
  • 需要额外的配置才能启用
  • 调试时可能不如查询参数直观
  • 某些代理服务器可能对包含分号的 URL 处理不当

总结 🎉

Matrix Variables 是 Spring MVC 提供的一个强大特性,它让我们能够以更语义化的方式在 URL 路径中传递参数。虽然不如查询参数常用,但在特定场景下能够提供更清晰、更 RESTful 的 API 设计。

关键要点:

  • 📍 参数直接嵌入路径段,语义更清晰
  • 🔧 需要显式配置启用
  • 🎯 特别适合资源特定的参数传递
  • 🚀 支持多值、默认值、可选参数等高级特性

选择使用 Matrix Variables 还是 Query Parameters,取决于你的具体业务场景和 API 设计理念。记住,好的 API 设计不仅要功能完善,更要语义清晰、易于理解!