Appearance
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 Variables | Query Parameters |
---|---|---|
位置 | 嵌入在路径段中 | URL 末尾 |
语法 | /path;key=value | /path?key=value |
语义 | 与特定资源强关联 | 通用查询条件 |
缓存友好 | 更友好(路径的一部分) | 一般 |
SEO友好 | 更友好 | 一般 |
复杂度 | 需要额外配置 | 开箱即用 |
最佳实践建议 ✅
使用建议
- 资源特定参数:当参数与特定资源密切相关时,使用 Matrix Variables
- 版本控制:API 版本信息可以通过 Matrix Variables 传递
- 过滤条件:对资源进行细粒度过滤时特别有用
- 配置参数:资源的展示格式、访问模式等配置
注意事项
- Matrix Variables 不如 Query Parameters 常见,团队成员可能不熟悉
- 需要额外的配置才能启用
- 调试时可能不如查询参数直观
- 某些代理服务器可能对包含分号的 URL 处理不当
总结 🎉
Matrix Variables 是 Spring MVC 提供的一个强大特性,它让我们能够以更语义化的方式在 URL 路径中传递参数。虽然不如查询参数常用,但在特定场景下能够提供更清晰、更 RESTful 的 API 设计。
关键要点:
- 📍 参数直接嵌入路径段,语义更清晰
- 🔧 需要显式配置启用
- 🎯 特别适合资源特定的参数传递
- 🚀 支持多值、默认值、可选参数等高级特性
选择使用 Matrix Variables 还是 Query Parameters,取决于你的具体业务场景和 API 设计理念。记住,好的 API 设计不仅要功能完善,更要语义清晰、易于理解!