一、HTTP 请求的结构

        在讨论怎么取参数之前,先看清参数藏在哪里。一个 HTTP 请求由三部分组成:请求行、请求头、请求体。

1.1 请求行

POST /api/v1/users/42/orders?page=1&size=20 HTTP/1.1

请求行包含三要素:

请求方法:POST,指定对资源的操作语义。

请求URL:/api/v1/users/42/orders?page=1&size=20,包含路径和查询字符串。

协议版本:HTTP/1.1,HTTP 协议版本号。

URL 中携带两种参数:
路径参数:嵌在路径段中,如 /users/42 中的 42。
查询参数:? 后面的 key=value 键值对,多个用 & 连接。

1.2 请求头

Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Accept: application/json
User-Agent: Mozilla/5.0
Cookie: sessionId=abc123
X-Forwarded-For: 192.168.1.100

请求头是 Key: Value 形式的元数据,不传输业务数据,但携带了格式声明、认证凭据、客户端信息等控制信息。常见请求头:

请求头 作用
Content-Type 声明请求体的数据格式
Authorization 携带认证凭据(Bearer Token、Basic 等)
Accept 告知服务器客户端期望的响应格式
Cookie 携带会话信息
X-Forwarded-For 代理场景下传递真实客户端 IP

1.3 请求体

请求体是可选部分,GET 和 DELETE 请求通常没有请求体,POST / PUT / PATCH 通常有。请求体的格式由 Content-Type 请求头声明:

Content-Type 格式 示例
application/json JSON {"name":"张三","age":25}
application/x-www-form-urlencoded 表单键值对 name=张三&age=25
multipart/form-data 多部分表单(文件上传) 二进制分片
text/plain 纯文本 hello world

1.4 完整请求结构

┌──────────────────────── 请求行 ────────────────────────┐
│  POST /api/v1/users/42/orders?page=1&size=20 HTTP/1.1 │
├──────────────────────── 请求头 ────────────────────────┤
│  Content-Type: application/json                        │
│  Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...        │
│  Accept: application/json                              │
├──────────────────────── 空行 ──────────────────────────┤
│                                                        │
├──────────────────────── 请求体 ────────────────────────┤
│  {                                                     │
│    "productId": 1001,                                  │
│    "quantity": 2,                                      │
│    "remark": "不要辣"                                  │
│  }                                                     │
└────────────────────────────────────────────────────────┘

我们一般会将参数放在以下四种位置:

路径参数 → 请求行 URL 路径段;查询参数 → 请求行 ? 后面;请求头参数 → 请求头区域;请求体参数 → 请求体区域。

二、方式一:路径参数 —— @PathVariable

路径参数是 URL 路径中通过 {} 占位声明的变量,Spring 通过 @PathVariable 将其提取并绑定到方法参数。

2.1 基本用法

@GetMapping("/users/{id}")
public User getUser(@PathVariable("id") Long id) {
    return userService.getById(id);
}

请求:GET /users/42 → id = 42
当方法参数名与占位符名一致时,可以省略 value(需编译时开启 -parameters):

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { ... }

2.2 多个路径参数

@DeleteMapping("/users/{userId}/orders/{orderId}")
public Result deleteOrder(@PathVariable Long userId,
                          @PathVariable Long orderId) {
    orderService.delete(userId, orderId);
    return Result.success();
}

请求:DELETE /users/42/orders/1001 → userId = 42, orderId = 1001

2.3 适用场景

路径参数用于标识资源或资源状态,属于 URL 路径的一部分,语义上是"你要操作哪个东西":

场景 示例 URL 路径参数
获取单个资源 GET /users/42 id=42
切换状态 PUT /orders/1001/status/2 id=1001, status=2
嵌套资源操作 DELETE /categories/5/dishes/10 DELETE /categories/5/dishes/10

三、方式二:查询参数 —— @RequestParam / 省略注解

查询参数是 URL 中 ? 后面的 key=value 键值对,Spring 提供了多种接收方式。

3.1 显式指定:@RequestParam

@GetMapping("/products")
public List<Product> list(@RequestParam("category") String category,
                          @RequestParam("page") int page) {
    return productService.list(category, page);
}

请求:GET /products?category=electronics&page=2
@RequestParam 的三个常用属性:

@GetMapping("/search")
public Result search(
    @RequestParam(value = "keyword", required = true) String keyword,
    @RequestParam(value = "page", defaultValue = "1") int page,
    @RequestParam(value = "size", defaultValue = "20") int size
) { ... }

3.2 省略注解

当方法参数是简单类型(基本类型及其包装类、String、Date 等)且不加任何注解时,Spring 默认按查询参数名匹配:

@GetMapping("/products")
public List<Product> list(String keyword, int page) {
    // 等价于 @RequestParam("keyword") 和 @RequestParam("page")
    return productService.list(keyword, page);
}

注意:省略注解要求方法参数名与 URL 查询参数名一致。这依赖编译时的 -parameters 选项。如果未开启,Spring 无法通过反射获取参数名,会报错。Maven 配置:

> <plugin>
>     <groupId>org.apache.maven.plugins</groupId>
>     <artifactId>maven-compiler-plugin</artifactId>
>     <configuration>
>         <parameters>true</parameters>
>     </configuration>
> </plugin>
>

3.3 对象封装:POJO 接收多个查询参数

查询参数多的时候,逐个声明方法参数会很冗长。Spring 支持将查询参数自动封装到 POJO 中:

@GetMapping("/products")
public PageResult<Product> list(ProductQueryDTO query) {
    return productService.pageQuery(query);
}
@Data
public class ProductQueryDTO {
    private String keyword;
    private Integer categoryId;
    private int page = 1;
    private int size = 20;
    private String sortField;
    private String sortOrder;
}

请求:GET /products?keyword=手机&categoryId=5&page=2&size=10
Spring 遍历查询参数,按名称匹配 POJO 的字段,通过 setter 注入。POJO 要求:有无参构造器 + 有标准 setter(Lombok @Data 满足)。

3.4 接收数组和集合

查询参数允许同名多值,Spring 可以用数组或 List 接收:

// 请求:GET /products?tag=电子&tag=热销&tag=新品
@GetMapping("/products")
public List<Product> list(@RequestParam("tag") List<String> tags) {
    return productService.findByTags(tags);
}

也可以用 @RequestParam 接收 Map<String, String> 获取所有查询参数:

@GetMapping("/search")
public Result search(@RequestParam Map<String, String> params) {
    params.forEach((k, v) -> System.out.println(k + "=" + v));
    return Result.success();
}

3.5 适用场景

查询参数用于筛选、分页、排序、搜索等,语义上是"你要什么样的资源":

场景 示例 URL
分页查询 GET /users?page=1&size=20
条件筛选 GET /products?category=电子&priceMax=5000
关键字搜索 GET /search?keyword=手机
排序 GET /products?sort=price&order=desc

四、方式三:请求体参数 —— @RequestBody

请求体参数位于 HTTP 请求的 body 区域,通常是 JSON 格式。Spring 通过 @RequestBody 触发 HttpMessageConverter(默认 Jackson 的 MappingJackson2HttpMessageConverter)将 JSON 反序列化为 Java 对象。


4.1 基本用法

@PostMapping("/users")
public Result createUser(@RequestBody UserDTO userDTO) {
    userService.create(userDTO);
    return Result.success();
}

请求:

POST /users
Content-Type: application/json

{
    "username": "zhangsan",
    "email": "zhangsan@example.com",
    "age": 25
}

Spring 读取请求体中的 JSON 字节流,通过 Jackson 将其反序列化为 UserDTO 对象。反序列化要求:
POJO 有无参构造器
字段名与 JSON key 一致(或通过 @JsonProperty 映射)
有标准 setter(或使用 Lombok @Data)

4.2 JSON 字段名与 Java 字段名不一致时

用 @JsonProperty 做映射:

@Data
public class UserDTO {
    @JsonProperty("user_name")
    private String userName;

    @JsonProperty("email_address")
    private String emailAddress;
}

前端传:

{
    "user_name": "张三",
    "email_address": "zhangsan@qq.com"
}

4.3 接收嵌套 JSON

@Data
public class OrderDTO {
    private Long addressId;
    private String remark;
    private List<OrderItemDTO> items;  // 嵌套对象列表
}

@Data
public class OrderItemDTO {
    private Long productId;
    private Integer quantity;
}

前端传:

{
    "addressId": 1,
    "remark": "不要辣",
    "items": [
        {"productId": 1001, "quantity": 2},
        {"productId": 1002, "quantity": 1}
    ]
}

Jackson 会递归反序列化嵌套对象,无需额外注解。

4.4 接收非 JSON 格式的请求体

@RequestBody 不仅仅用于 JSON。Spring 会根据 Content-Type 选择对应的 HttpMessageConverter:

Content-Type 使用的 Converter 用法
application/json MappingJackson2HttpMessageConverter 反序列化为 POJO
application/xml MappingJackson2XmlHttpMessageConverter 反序列化为 POJO
text/plain StringHttpMessageConverter 接收为 String
// 接收纯文本请求体
@PostMapping("/notify")
public Result handleNotify(@RequestBody String payload) {
    log.info("收到通知:{}", payload);
    return Result.success();
}

4.5 @RequestBody 不能省略

这是一个高频踩坑点。如果省略 @RequestBody:

// 错误:省略了 @RequestBody
@PostMapping("/users")
public Result createUser(UserDTO userDTO) {
    // userDTO 的所有字段都是 null
    return Result.success();
}

Spring 对没有 @RequestBody 注解的复杂类型参数,执行的是表单参数绑定(WebDataBinder),即从 application/x-www-form-urlencoded 格式的查询参数 / 表单参数中按字段名匹配。前端传的是 JSON 请求体,Spring 根本不会去读它,结果所有字段为 null。
规则:只要前端用 application/json 传数据,Controller 方法参数必须加 @RequestBody,没有例外。

4.6 一个方法只能有一个 @RequestBody

HTTP 请求体只能被读取一次,Spring 不支持用多个 @RequestBody 参数分别接收不同部分。如果需要传递多个逻辑对象,封装到一个外层 DTO:

// 错误:两个 @RequestBody
@PostMapping("/order")
public Result create(@RequestBody OrderDTO order, @RequestBody PaymentDTO payment) { ... }

// 正确:封装到外层 DTO
@Data
public class CreateOrderRequest {
    private OrderDTO order;
    private PaymentDTO payment;
}

@PostMapping("/order")
public Result create(@RequestBody CreateOrderRequest request) { ... }

4.7 适用场景

请求体参数用于提交、修改复杂数据,语义上是"我要给你什么数据"

场景 HTTP 方法 说明
新增资源 POST + JSON 创建用户、提交订单
修改资源 PUT / PATCH + JSON 修改个人信息、更新配置
批量操作 POST + JSON 数组 批量导入、批量删除

五、方式四:请求头参数 —— @RequestHeader

请求头中的值通过 @RequestHeader 提取。虽然业务数据一般不放在请求头中,但认证凭据、追踪标识、客户端信息等经常通过请求头传递。


5.1 基本用法

@GetMapping("/profile")
public Result getProfile(@RequestHeader("Authorization") String authHeader) {
    String token = authHeader.replace("Bearer ", "");
    Long userId = jwtUtil.parseUserId(token);
    return Result.success(userService.getById(userId));
}

请求:

GET /profile HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

5.2 常用属性


@RequestHeader 与 @RequestParam 的属性完全一致:

属性 作用 默认值
value / name 请求头的 key 方法参数名
required 是否必传 true(不存在时抛 400
defaultValue 未传时的默认值

5.3 获取所有请求头

如果需要获取多值请求头(如 Accept 可以有多个值),使用 MultiValueMap:

@GetMapping("/headers")
public Result headers(@RequestHeader MultiValueMap<String, String> headers) {
    List<String> acceptValues = headers.get("Accept");
    return Result.success();
}

5.4 拦截器中手动获取请求头


在认证拦截器中,通常不用 @RequestHeader,而是直接从 HttpServletRequest 获取:

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {
        String token = request.getHeader("Authorization");
        if (token == null || !jwtUtil.validate(token)) {
            response.setStatus(401);
            return false;
        }
        return true;
    }
}

5.5 适用场景

请求头参数不是业务数据,而是控制信息:

场景 请求头
身份认证 Authorization: Bearer xxx
请求追踪 X-Trace-Id: abc-123
多语言支持 Accept-Language: zh-CN
内容协商 Accept: application/json
客户端标识 X-Device-Type: iOS
幂等性控制 Idempotency-Key: unique-uuid

六、总结

参数方式 注解 从哪取 传什么
路径参数 @PathVariable URL 路径段 资源标识
查询参数 @RequestParam / 省略 URL ? 后 筛选、分页、排序
请求体参数 @RequestBody 请求体 提交的复杂数据
请求头参数 @RequestHeader 请求头 认证、追踪等控制信息

记住一个核心判断逻辑:
要定位资源 → 路径参数
要筛选资源 → 查询参数
要提交数据 → 请求体参数(加 @RequestBody)
要传递控制信息 → 请求头参数

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐