1. 概述

在 Spring MVC 开发中,@RequestParam@PathVariable 是两个高频使用的注解,用于从 HTTP 请求中提取参数。虽然它们都能获取请求中的数据,但适用场景和行为有本质区别。

本文将结合实际示例,帮你彻底理清两者的差异,避免日常开发中“踩坑”。


2. 查询参数 vs 路径变量

✅ 核心区别一句话总结:
@RequestParam 用于获取 URL 中的查询参数(query parameter),而 @PathVariable 用于提取 URI 模板中的路径变量(path variable)

使用 @PathVariable 提取路径变量

假设我们有一个根据 ID 获取资源的接口:

@GetMapping("/foos/{id}")
@ResponseBody
public String getFooById(@PathVariable String id) {
    return "ID: " + id;
}

此时,URL 路径中的 {id} 是一个占位符,Spring 会自动将其绑定到 @PathVariable 注解的参数上。

访问如下地址:

http://localhost:8080/spring-mvc-basics/foos/abc

输出结果为:

ID: abc

使用 @RequestParam 获取查询参数

同样的功能也可以通过查询参数实现:

@GetMapping("/foos")
@ResponseBody
public String getFooByIdUsingQueryParam(@RequestParam String id) {
    return "ID: " + id;
}

对应的请求 URL 变为:

http://localhost:8080/spring-mvc-basics/foos?id=abc

输出结果相同:

ID: abc

📌 总结对比:

特性 @PathVariable @RequestParam
来源 URI 路径模板 查询字符串(query string)
示例 URL /foos/abc /foos?id=abc
是否必须编码 否(原始值) 是(自动解码)

3. 编码处理:原始值 vs 解码值

⚠️ 这是一个容易被忽视但非常关键的差异!

由于 @PathVariable 从 URI 路径中提取值,该值不会被自动 URL 解码
@RequestParam 获取的是查询参数,Spring 会自动对其进行 URL 解码

示例说明

假设我们要传递的值包含 + 符号(代表空格)。

使用 @PathVariable

http://localhost:8080/spring-mvc-basics/foos/ab+c

对应方法:

@GetMapping("/foos/{id}")
@ResponseBody
public String getFooById(@PathVariable String id) {
    return "ID: " + id;
}

输出结果为:

ID: ab+c

👉 + 没有被解码,保持原样。

使用 @RequestParam

http://localhost:8080/spring-mvc-basics/foos?id=ab+c

对应方法:

@GetMapping("/foos")
@ResponseBody
public String getFooByIdUsingQueryParam(@RequestParam String id) {
    return "ID: " + id;
}

输出结果为:

ID: ab c

👉 + 被自动解码为空格。

💡 原因:在 URL 编码中,+ 表示空格,仅适用于 query 部分。路径部分的 + 不会被自动转换。

所以如果你在路径中需要传递特殊字符,建议手动编码(如用 %20 替代空格),否则可能拿到非预期的值。


4. 可选参数的处理

✅ 两者都支持可选参数,但实现方式和注意事项不同。

@RequestParam 的可选性

这是常见操作,通过 required = false 设置:

@GetMapping("/foos")
@ResponseBody
public String getFooWithOptionalParam(@RequestParam(required = false) String id) {
    return "ID: " + (id != null ? id : "default");
}

访问 /foos 时,idnull,可做默认处理。

@PathVariable 的可选性

⚠️ 注意:@PathVariable 的可选支持是从 Spring 4.3.3 开始才有的。

使用方式:

@GetMapping({"/myfoos/optional", "/myfoos/optional/{id}"})
@ResponseBody
public String getFooByOptionalId(@PathVariable(required = false) String id) {
    return "ID: " + id;
}

支持两种访问方式:

http://localhost:8080/spring-mvc-basics/myfoos/optional/abc

输出:

ID: abc
http://localhost:8080/spring-mvc-basics/myfoos/optional

输出:

ID: null

❌ 踩坑提醒:
当使用可选 @PathVariable 时,必须提供多个 @GetMapping 路径(如上面的数组形式),否则无法匹配无参数的路径。
而且要小心路径冲突,比如:

@GetMapping("/users/{id}")
@GetMapping("/users/profile")

如果 {id} 是可选的,这两个路径可能产生歧义,导致请求匹配错乱。


5. 总结

对比项 @RequestParam @PathVariable
参数来源 查询字符串(?key=value) URI 路径模板(/path/{var})
是否自动解码 ✅ 是 ❌ 否(保留原始值)
是否可选 ✅ 支持 required = false ✅ Spring 4.3.3+ 支持
RESTful 风格推荐 ❌ 一般不用于核心资源标识 ✅ 推荐用于资源定位
典型用途 过滤、分页、搜索参数 资源 ID、层级路径

📌 简单粗暴记忆法:

  • 要找的东西在 ? 后面 → @RequestParam
  • 要找的东西在 / 路径里 → @PathVariable

项目源码已整理至 GitHub:https://github.com/example/spring-mvc-basics(mock 地址)

掌握这两个注解的区别,能让你在设计 REST 接口时更清晰、更规范,少走弯路。


原始标题:Spring @RequestParam vs @PathVariable Annotations | Baeldung