Skip to content

SpringMVC教程 - 5 参数绑定

前端或其他客户端在请求接口的时候,肯定会传递参数,这一章主要讲解在 Controller 中如何获取传递的参数。SpringMVC的参数绑定机制可以自动将HTTP请求参数绑定到Controller方法的参数上,大大简化了开发工作。

下面就开始介绍在 Controller 中如何接收数据。


5.1 基本数据类型绑定

1 使用方法

可以直接在方法参数上使用 @RequestParam 注解即可。

举个栗子:

java
package com.foooor.hellospringmvc.controller;

import com.foooor.hellospringmvc.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j  // 使用lombok引入日志
@RestController
@RequestMapping("/students") // 类级别映射前缀
public class StudentController {

    /**
     * 创建学生
     * POST /students
     */
    @PostMapping
    public Result createStudent(@RequestParam("stuNo") String stuNo,
                                @RequestParam("name") String name,
                                @RequestParam("age") int age) {
        log.info("createStudent: stuNo={}, name={}, age={}", stuNo, name, age);
        return Result.success("创建学生");
    }

    // 其他代码...
}
  • 在参数上通过 @RequestParam 指定传递的参数,所以调用的时候,名称要与 @RequestParam("xxx") 中的参数 xxx 对应。
  • @RequestParam("name") 也可以写为 @RequestParam(value="name")

测试一下:

下面使用 Apifox 请求该接口,并在 body 中,使用 x-www-form-urlencoded 的方式传递参数:

请求后,在控制台可以打印 Controller 中的参数:

createStudent: stuNo=101001, name=foooor, age=18

可以看到非常容易就获取到参数了。

需要注意@RequestParam 注解不支持使用 JSON 格式的请求体,上面的请求体格式是 <form> 表单的方式提交的。


2 省略@RequestParam

如果方法的参数和传递的参数一致,还可以省略 @RequestParam 注解。

那么方法如下:

java
/**
 * 创建学生
 * POST /students
 */
@PostMapping
public Result createStudent(String stuNo, String name, int age) {
    log.info("createStudent: stuNo={}, name={}, age={}", stuNo, name, age);
    return Result.success("创建学生");
}
  • 传递的时候,只要参数和方法参数对应即可。

但是在 Spring6 中,需要首先在 pom.xml 中添加一下编译配置:

xml
<build>
    <plugins>
        <!-- 其他配置... -->

        <!-- 编译配置 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <source>17</source>
                <target>17</target>
                <compilerArgs>
                    <arg>-parameters</arg>  <!-- 开启参数名保留,用于反射获取参数名 -->
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

然后,使用 Maven clean 一下项目:

然后再尝试一下请求接口,查看一下 Controller 中是否可以接收到参数。

3 必须选项

在使用 @RequestParam 注解的时候,默认参数是必传的,如果不传递会报错。

如果允许不传递参数,可以使用 required=false 来标识非必须。

举个栗子:

java
@PostMapping
    public Result createStudent(@RequestParam("stuNo") String stuNo,
                                @RequestParam(value="name", required = false) String name,  // 可选参数,默认值为 null
                                @RequestParam("age") int age) {
   // ...
}
  • 参数默认是必须的,但是允许传递空字符串。

4 默认值

对于那些允许不传递值,就使用默认值的参数,可以使用 defaultValue 属性指定默认值。

defaultValue 一旦设置,则 required 自动视为 false。 所以不需要再写 required = false

java
@PostMapping
    public Result createStudent(@RequestParam("stuNo") String stuNo,
                                @RequestParam(value="name", defaultValue = "foooor") String name,
                                @RequestParam("age") int age) {
   // ...
}

5.2 对象参数绑定

当需要处理的参数较多时,使用对象来封装这些参数会更加清晰和易于维护。SpringMVC支持将请求参数自动绑定到 JavaBean 对象上。

先创建一个 JavaBean 对象,例如我创建一个 StudentDto.java,如下:

java
package com.foooor.hellospringmvc.dto;

import lombok.Data;

@Data
public class StudentDto {

    private String stuNo;
    private String name;
    // 建议使用 Integer:未传时为 null,传值时为具体数字
    private Integer age;

}

DTO(Data Transfer Object)是用于在不同层之间传输数据的对象,通常用于解耦前后端交互或服务间通信,避免直接暴露业务模型。一般不太建议使用与数据库的映射对象来作为数据传输对象,因为可能存在字段不一致、暴露敏感字段等问题。


然后修改 Controller 中的方法,将接收参数直接改为对象即可:

java
/**
 * 创建学生
 * POST /students
 */
@PostMapping
public Result createStudent(StudentDto student) {
    log.info("createStudent: student={}", student);
    return Result.success("创建学生");
}

测试的时候,和上面的一样,请求的时候需要使用 x-www-form-urlencoded 格式的请求体,Controller中才可以获取到参数。

需要注意,传递的参数名称需要和对象中的名称对应,更准确的说法,是需要和对象中的setter方法名称对象,传递参数为 xxx,类中需要有 setXxx 方法。

5.3 @RequestBody接收JSON格式数据

上面的 @RequestParam 注解和接收对象参数的方式都不支持调用者使用 JSON 请求体传递参数。

但是现在的前端请求,一般都是通过 JSON 格式的请求体来传递参数的,所以这里就需要用到一个注解 @RequestBody

例如前端或调用者通过如下方式调用接口,body 数据格式选择 JSON:

Controller 的方法参数需要使用 @RequestBody 注解:

java
/**
 * 创建学生
 * POST /students
 */
@PostMapping
public Result createStudent(@RequestBody StudentDto student) {
    log.info("createStudent: student={}", student);
    return Result.success("创建学生");
}

SpringMVC 会使用 HttpMessageConverter 完成数据的反序列化(JSON字符串转对象),默认使用 MappingJackson2HttpMessageConverter 来解析 JSON 字符串。


@RequestBody 还支持集合、Map类型,所以上面的方法还可以这样写:

java
/**
 * 创建学生
 * POST /students
 */
@PostMapping
public Result createStudent(@RequestBody Map<String, Object> student) {
    log.info("createStudent: student={}", student);
    return Result.success("创建学生");
}
  • 通过 Map 接收参数。

如果请求的格式是集合格式,例如:

java
// 请求体:
[
  {"stuNo": "101002", "name": "Tom", "age": 20},
  {"stuNo": "101002", "name": "Jerry", "age": 22}
]

那么 Controller 中可以使用 List 来接收:

java
/**
 * 创建学生
 * POST /students
 */
@PostMapping
public Result createStudent(@RequestBody List<StudentDto> students) {
    // ...略
}
  • 从上面也可以看到@RequestBody 是支持数据格式的嵌套的,也就是对象属性也是对象,只要和 JSON 格式对应即可。

5.4 @RequestHeader接收请求的header数据

在发送请求数据的时候,包括请求头和请求体,一般情况下请求数据是放在请求体中的。我们还可以将一些数据放在请求头中,那么在 Controller 中,可以通过 @RequestHeader 注解来接收参数。

举个栗子:

java
/**
 * 创建学生
 * POST /students
 */
@PostMapping
public Result createStudent(@RequestBody StudentDto student, @RequestHeader("token") String token) {
    log.info("createStudent: students={}, token={}", student, token);
    return Result.success("创建学生");
}
  • 我们在请求的时候,可以在请求头中添加 token 属性(Apifox也可以添加),在Controller中可以通过 @RequestHeader("token") 接收到数据。
  • @RequestHeader 同样支持 valuerequireddefaultValue 属性,和 @RequestParam 注解是一样的。

SpringMVC 还有一个 @CookieValue 注解,用于接收 Cookie 参数, 使用方法和 @RequestHeader 类似,了解一下。

5.5 RESTful API完善

前面实现 Restful 风格 API 的时候,没有传递参数,现在学习了参数绑定,我们可以将方法的参数添加上:

  1. 列表查询的方法,需要传递查询参数;
  2. 新增和修改需要传递具体的信息。

具体如下:

java
package com.foooor.hellospringmvc.controller;

import com.foooor.hellospringmvc.common.Result;
import com.foooor.hellospringmvc.dto.Student;
import com.foooor.hellospringmvc.dto.StudentDto;
import com.foooor.hellospringmvc.dto.StudentQueryDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/students")
@Validated
public class StudentController {

    /**
     * 查询学生列表(分页+条件)
     * GET /students?name=xxx&email=xxx&page=1&size=20
     * 可以通过创建StudentQueryDto类来接收查询参数
     */
    @GetMapping
    public Result listStudents(StudentQueryDto queryDto) {
        log.info("查询学生列表, 参数: {}", queryDto);

        // TODO 调用Service查询学生列表,然后返回
        List<Student> studentList = new ArrayList<>();

        return Result.success(studentList);
    }

    /**
     * 查询学生详情
     * GET /students/{id}
     */
    @GetMapping("/{id}")
    public Result getStudentDetail(@PathVariable("id") Long id) {
        log.info("查询学生详情, ID: {}", id);

        // TODO 调用Service查询学生详情,然后返回
        // 模拟返回数据
        Student student = null;

        return Result.success(student);
    }

    /**
     * 创建学生
     * POST /students
     */
    @PostMapping
    public Result createStudent(@RequestBody StudentDto createDto) {
        log.info("创建学生, 参数: {}", createDto);

        // TODO 调用Service创建学生

        return Result.success();
    }

    /**
     * 更新学生
     * PUT /students/{id}
     */
    @PutMapping("/{id}")
    public Result updateStudent(@PathVariable("id") Long id, @RequestBody StudentDto updateDto) {
        log.info("更新学生, ID: {}, 参数: {}", id, updateDto);

        // TODO 调用Service更新学生信息

        // 模拟更新操作
        return Result.success();
    }

    /**
     * 删除学生
     * DELETE /students/{id}
     */
    @DeleteMapping("/{id}")
    public Result deleteStudent(@PathVariable("id") Long id) {
        log.info("删除学生, ID: {}", id);

        // TODO 调用Service删除学生

        return Result.success();
    }

    /**
     * 批量删除学生
     * DELETE /students/batch?ids=1&ids=2&ids=3
     */
    @DeleteMapping("/batch")
    public Result batchDeleteStudents(@RequestParam List<Long> ids) {
        log.info("批量删除学生, IDs: {}", ids);

        // TODO 调用Service删除学生

        // 返回删除成功结果
        return Result.success();
    }
}
  • 针对查询参数,我们可以创建一个查询参数的类 StudentQueryDto ,前端传递参数,使用这个类的对象来接收,然后根据传递的查询条件查询学生列表返回。
  • 同样针对具体的学生信息,也是创建了 StudentDto 来接收信息。

一般在项目中,会涉及到三种对象,一种是 pojo/Entity 实体类,类中的属性和数据库中的字段是对应的,在执行数据库查询的时候,返回的是这个对象,但是一般不会将对象直接返回给前端,因为其中的字段可能存在敏感信息(例如密码),所以一般会将其转换为 VO 对象。

VO(View Object)用于返回给前端的数据(输出层),负责封装和展示数据,隐藏敏感字段,如查询结果UserVO 。

而 DTO(Data Transfer Object)用于接收前端传入的数据(输入层),负责参数校验和数据转换,如注册时的UserRegisterDTO。

但是项目中具体怎么用,还是要结合实际情况,因为有的人觉得太繁琐了,直接使用 pojo。