Skip to content

Spring WebFlux Matrix Variables 深度解析

🎯 什么是 Matrix Variables?

Matrix Variables(矩阵变量)是 Spring WebFlux 中一种特殊的参数传递方式,它允许我们在 URL 路径段中嵌入键值对参数。这个概念基于 RFC 3986 规范,由万维网之父 Tim Berners-Lee 在一篇古老的博文中首次提出。

NOTE

Matrix Variables 也被称为 URI 路径参数,它们与传统的查询参数(Query Parameters)不同,是嵌入在路径段中的参数。

🤔 为什么需要 Matrix Variables?

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

/pets/42?color=red&year=2012

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

kotlin
// URL: /pets/42?color=red&year=2012
@GetMapping("/pets/{petId}")
fun findPet(
    @PathVariable petId: String,
    @RequestParam color: String,
    @RequestParam year: Int
) {
    // 处理逻辑
}
kotlin
// URL: /pets/42;color=red;year=2012
@GetMapping("/pets/{petId}")
fun findPet(
    @PathVariable petId: String,
    @MatrixVariable color: String, 
    @MatrixVariable year: Int
) {
    // 处理逻辑
}

📝 Matrix Variables 语法规则

Matrix Variables 遵循特定的语法规则:

基本语法

/path;key=value;key2=value2

多值支持

Matrix Variables 支持两种多值表示方式:

多值表示方式

  1. 逗号分隔/cars;color=red,green,blue
  2. 重复变量名/cars;color=red;color=green;color=blue

🛠️ 实际应用场景

场景一:基础参数获取

kotlin
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
fun findPet(
    @PathVariable petId: String,
    @MatrixVariable q: Int
) {
    println("Pet ID: $petId")  // Pet ID: 42
    println("Quality: $q")     // Quality: 11
    
    // 根据宠物ID和质量参数查询宠物信息
    return petService.findPetWithQuality(petId, q)
}

场景二:多路径段参数区分

当 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") ownerQuality: Int, 
    @MatrixVariable(name = "q", pathVar = "petId") petQuality: Int
) {
    println("Owner Quality: $ownerQuality")  // Owner Quality: 11
    println("Pet Quality: $petQuality")      // Pet Quality: 22
    
    // 根据主人和宠物的不同质量参数进行查询
    return petService.findPetByOwnerAndPetQuality(ownerQuality, petQuality)
}

IMPORTANT

当多个路径段包含同名 Matrix Variables 时,必须使用 pathVar 属性明确指定变量所属的路径段。

场景三:可选参数与默认值

kotlin
// GET /pets/42 (没有 Matrix Variables)
@GetMapping("/pets/{petId}")
fun findPet(
    @PathVariable petId: String,
    @MatrixVariable(required = false, defaultValue = "1") priority: Int
) {
    println("Pet ID: $petId")      // Pet ID: 42
    println("Priority: $priority")  // Priority: 1 (默认值)
    
    return petService.findPetWithPriority(petId, priority)
}

场景四:获取所有 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("所有 Matrix Variables: $matrixVars")
    println("Pet 路径段的 Matrix Variables: $petMatrixVars")
    
    return petService.findPetWithAllParams(matrixVars, petMatrixVars)
}

🔄 与传统查询参数的对比

让我们通过一个实际的电商搜索场景来对比两种方式:

kotlin
// URL: /products/search?category=electronics&brand=apple&color=black&price=1000
@GetMapping("/products/search")
fun searchProducts(
    @RequestParam category: String,
    @RequestParam brand: String,
    @RequestParam color: String,
    @RequestParam price: Int
): List<Product> {
    return productService.search(category, brand, color, price)
}
kotlin
// URL: /products/electronics;brand=apple;color=black;price=1000
@GetMapping("/products/{category}")
fun searchProducts(
    @PathVariable category: String,
    @MatrixVariable brand: String,
    @MatrixVariable color: String,
    @MatrixVariable price: Int
): List<Product> {
    return productService.search(category, brand, color, price)
}

TIP

Matrix Variables 方式使 URL 更加语义化,category 作为路径的一部分更直观,而其他过滤条件作为该路径段的属性。

⚠️ 重要注意事项

WebFlux vs Spring MVC 的差异

WARNING

在 Spring WebFlux 中,Matrix Variables 的存在与否不会影响请求映射,这与 Spring MVC 不同。

kotlin
// 在 WebFlux 中,以下两个 URL 都会匹配到同一个处理方法
// /pets/42
// /pets/42;color=red;year=2012

@GetMapping("/pets/{petId}")
fun findPet(
    @PathVariable petId: String,
    @MatrixVariable(required = false) color: String? = null
) {
    // 处理逻辑
}

配置要求

Matrix Variables 配置示例
kotlin
@Configuration
class WebConfig : WebFluxConfigurer {
    
    override fun configurePathMatching(configurer: PathMatchConfigurer) {
        // 启用 Matrix Variables 支持
        configurer.setUseTrailingSlashMatch(true)
    }
}

🎯 最佳实践

1. 语义化 URL 设计

kotlin
// ✅ 推荐:语义清晰
// /api/v1/users/123;include=profile,settings;format=json
@GetMapping("/api/v1/users/{userId}")
fun getUserDetails(
    @PathVariable userId: String,
    @MatrixVariable include: List<String>,
    @MatrixVariable(defaultValue = "json") format: String
) {
    // 处理逻辑
}

// ❌ 不推荐:语义不清
// /api/v1/data;id=123;type=user;include=profile

2. 合理使用默认值

kotlin
@GetMapping("/products/{category}")
fun getProducts(
    @PathVariable category: String,
    @MatrixVariable(required = false, defaultValue = "10") limit: Int,      
    @MatrixVariable(required = false, defaultValue = "price") sortBy: String
) {
    return productService.getProducts(category, limit, sortBy)
}

3. 复杂参数处理

kotlin
data class SearchCriteria(
    val brand: String?,
    val color: String?,
    val priceRange: String?,
    val inStock: Boolean = true
)

@GetMapping("/products/{category}")
fun searchProducts(
    @PathVariable category: String,
    @MatrixVariable matrixVars: MultiValueMap<String, String>
): List<Product> {
    val criteria = SearchCriteria(
        brand = matrixVars.getFirst("brand"),
        color = matrixVars.getFirst("color"),
        priceRange = matrixVars.getFirst("price"),
        inStock = matrixVars.getFirst("inStock")?.toBoolean() ?: true
    )
    
    return productService.searchWithCriteria(category, criteria)
}

📚 总结

Matrix Variables 为 Spring WebFlux 提供了一种优雅的参数传递方式,特别适合以下场景:

适用场景

  • 需要语义化 URL 设计
  • 参数与特定路径段密切相关
  • 需要在同一 URL 中传递层次化参数
  • RESTful API 设计中的资源过滤

不适用场景

  • 简单的查询参数
  • 需要在表单中提交的数据
  • 大量参数的传递

NOTE

Matrix Variables 是一个强大的工具,但应该根据具体业务场景合理选择使用。它们最适合用于创建更加语义化和结构化的 URL。

通过合理使用 Matrix Variables,我们可以设计出更加直观、语义化的 API 接口,提升代码的可读性和维护性。 🚀