我们来详细分析 Spring MVC 中的 @PathVariable
注解。
@PathVariable
注解的作用
@PathVariable
注解用于从 URI 模板 (URI Template)中提取值,并将这些值绑定到 Controller 方法的参数上。URI 模板是一种包含占位符的 URL 路径,这些占位符表示路径中的动态部分。
这在构建 RESTful Web 服务时非常常见,其中资源的标识符(如 ID)通常是 URL 路径的一部分,而不是查询参数。
例如:
在一个像 /users/123
这样的 URL 中,123
就是一个动态部分,代表用户的 ID。
基本用法
- 在
@RequestMapping
(或其变体) 中定义 URI 模板 : 使用花括号{}
来定义占位符(模板变量)。例如:@GetMapping("/users/{id}")
。 - 在方法参数上使用
@PathVariable
注解 : 将需要绑定路径变量值的方法参数标记为@PathVariable
。 - 名称匹配 :
- 如果方法参数名与 URI 模板中的占位符名称相同 ,可以省略
@PathVariable
的name
(或value
) 属性。 - 如果名称不同 ,则必须使用
name
(或value
) 属性来指定要绑定的占位符名称。
- 如果方法参数名与 URI 模板中的占位符名称相同 ,可以省略
示例 1:名称匹配
java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/articles") // 类级别基础路径
public class ArticleController {
// 处理 GET /articles/{id} 请求
// 占位符名称 "id" 与方法参数名 "id" 相同
@GetMapping("/{id}")
@ResponseBody
public String getArticleById(@PathVariable Long id) {
// Spring 会自动将路径中的值(如 "42")转换为 Long 类型并赋给 id 参数
return "Fetching article with ID: " + id;
}
// 处理 GET /articles/category/{categoryName}
// 占位符名称 "categoryName" 与方法参数名 "categoryName" 相同
@GetMapping("/category/{categoryName}")
@ResponseBody
public String getArticlesByCategory(@PathVariable String categoryName) {
return "Fetching articles in category: " + categoryName;
}
}
- 当请求
GET /articles/42
时,getArticleById
方法会被调用,参数id
的值将是42L
。 - 当请求
GET /articles/category/technology
时,getArticlesByCategory
方法会被调用,参数categoryName
的值将是"technology"
。
示例 2:名称不匹配 (使用 name
或 value
属性)
java
import org.springframework.web.bind.annotation.RestController; // 使用 RestController 简化
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
@RequestMapping("/products")
public class ProductController {
// 处理 GET /products/item/{productId} 请求
// 占位符名称是 "productId",但方法参数名是 "prodId"
@GetMapping("/item/{productId}")
public String getProduct(@PathVariable(name = "productId") String prodId) {
return "Fetching product with ID: " + prodId;
}
// 使用 value 属性效果相同
@GetMapping("/info/{productCode}")
public String getProductInfo(@PathVariable(value = "productCode") String code) {
return "Fetching info for product code: " + code;
}
}
类型转换
Spring MVC 会自动尝试将从 URL 路径中提取的字符串值转换为方法参数声明的类型(如 int
, long
, double
, boolean
, String
, Long
, Integer
等)。
- 如果转换成功,方法将接收到正确类型的值。
- 如果转换失败(例如,路径是
/articles/abc
但方法参数是@PathVariable Long id
),Spring 会抛出TypeMismatchException
或类似的异常,通常导致客户端收到 HTTP 400 (Bad Request) 响应。
处理多个路径变量
一个 URL 路径可以包含多个占位符,你可以在方法签名中使用多个 @PathVariable
注解来分别获取它们的值。
java
@RestController
@RequestMapping("/orders")
public class OrderController {
// 处理 GET /orders/{orderId}/items/{itemId}
@GetMapping("/{orderId}/items/{itemId}")
public String getOrderItem(
@PathVariable Long orderId,
@PathVariable Long itemId) {
return String.format("Fetching item %d from order %d", itemId, orderId);
}
// 名称不匹配的例子
@GetMapping("/customer/{custId}/order/{oId}")
public String getCustomerOrder(
@PathVariable("custId") String customerIdentifier,
@PathVariable("oId") Long orderNumber) {
return String.format("Fetching order %d for customer %s", orderNumber, customerIdentifier);
}
}
- 请求
GET /orders/101/items/5
会调用getOrderItem
,orderId
为101L
,itemId
为5L
。 - 请求
GET /orders/customer/CUST-A/order/99
会调用getCustomerOrder
,customerIdentifier
为"CUST-A"
,orderNumber
为99L
。
可选的路径变量
@PathVariable
默认是必需的 。如果 URI 模板中的变量在实际请求 URL 中不存在(例如,请求 /users/
而不是 /users/123
),该映射根本不会匹配。
如果你需要处理路径变量可能存在也可能不存在的情况(这在 REST 设计中相对少见,通常会用不同的 URL),有几种方法:
-
定义两个不同的 Handler 方法: 一个处理带变量的路径,一个处理不带变量的路径。这是最清晰、最常见的方式。
java@RestController @RequestMapping("/reports") public class ReportController { // 处理 /reports/{year} @GetMapping("/{year}") public String getReportByYear(@PathVariable int year) { return "Report for year: " + year; } // 处理 /reports (没有年份) @GetMapping public String getDefaultReport() { return "Default report (all years or latest)"; } }
-
使用
java.util.Optional
(Spring 4.1+) : 可以将方法参数声明为Optional<T>
。如果路径变量存在且可以转换,Optional
会包含该值;如果路径变量不存在(但注意:这通常仍然要求路径结构匹配,只是变量值可能是某种形式的'空'或由框架处理,具体行为可能依赖版本和配置,最可靠的还是多映射方法 ),或者不能转换,Optional
会是空的。这种方式对于处理路径变量值 的可选性比处理路径段的可选性更常见。javaimport java.util.Optional; // ... @RestController @RequestMapping("/users") public class OptionalUserController { // 可能需要配合其他配置或特定路径模式才能完全匹配可选段 // 更典型的用法是路径段必须存在,但值可以处理 @GetMapping({"/find", "/find/{userId}"}) // 尝试用两个路径映射到同一个方法 public String findUser(@PathVariable(required = false) Optional<Long> userId) { if (userId.isPresent()) { return "Finding user with ID: " + userId.get(); } else { return "Finding all users or default user."; } } } // 注意:上面这种组合映射到一个方法,其行为和对 /find 的匹配可能不如分开映射清晰。 // 分开映射通常更推荐。
实践 : 对于可选的路径段 ,优先使用方法 1(定义两个 Handler)。
-
使用
@PathVariable(required = false)
: 这个属性通常不用于使路径段本身可选 。它更多地与 Matrix Variables (一种在路径段中嵌入键值对的方式,形式如/cars;color=red;year=2012
)相关,用于表示某个 matrix variable 是可选的。对于常规的路径变量,required=false
的行为可能不直观,不推荐用于使/users/{id}
中的id
段可选。
绑定到 Map
和 @RequestParam
类似,可以将所有的路径变量收集到一个 Map<String, String>
或 MultiValueMap<String, String>
中。
java
import java.util.Map;
import org.springframework.util.MultiValueMap;
// ...
@RestController
public class MapPathController {
// 处理 /mapvars/name/{name}/age/{age}
@GetMapping("/mapvars/name/{name}/age/{age}")
public String getVarsAsMap(@PathVariable Map<String, String> pathVars) {
// pathVars 会包含 {"name": "...", "age": "..."}
String name = pathVars.get("name");
String age = pathVars.get("age");
return String.format("Received via Map: Name=%s, Age=%s", name, age);
}
// MultiValueMap 主要用于 Matrix Variables,但也适用于普通路径变量
@GetMapping("/multivars/name/{name}/age/{age}")
public String getVarsAsMultiMap(@PathVariable MultiValueMap<String, String> pathVars) {
return "Received via MultiValueMap: " + pathVars.toString();
}
}
这种用法不如单独声明每个 @PathVariable
常见,但在某些动态场景下可能有用。
总结
@PathVariable
用于从 URL 路径(URI 模板变量)中提取值。- 通过在
@RequestMapping
或其变体中使用{placeholder}
定义模板变量。 - 默认情况下,方法参数名需要与占位符名匹配。
- 使用
name
或value
属性处理名称不匹配的情况。 - Spring 自动进行类型转换,失败会抛出异常 (HTTP 400)。
- 可以处理多个路径变量。
- 路径变量默认是必需的 ;处理可选路径段的最佳方式是定义多个 Handler 方法。
- 不要与
@RequestParam
混淆,后者用于提取查询参数 或表单数据。