概述:以一个web项目中的用户注册服务为例子,介绍使用java bean validation进行数据验证的过程。

1.引入依赖

从spring boot 2.3开始,spring-boot-starter-validation依赖独立出来了,2.3之前的版本,validation整合在spring-boot-starter-web中,具体原因可以参考这个issue,所以请根据当前使用的spring boot版本选择相应依赖:

  • spring boot 2.3+:
    1
    2
    3
    4
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    
  • spring boot 2.2及更早版本:
    1
    2
    3
    4
    
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
    

2.用注解对字段进行约束

注解可以自定义,大部分时候内置的就够用。内置的注解可以参考最新的jsr380,需要jdk8+支持,更早的jdk版本可以参考jsr303,jsr349等。

User类为例,用户名和密码不能为空,用户名长度4-20位:

1
2
3
4
5
6
7
8
9
public class User{
    private int uid;//用户ID

    @NotEmpty(message = "用户名不能为空")
    @Length(min=4,max=20,message = "用户名长度不符合要求,4-20位")
    private String username;//用户名

    @NotEmpty(message = "密码不能为空")
    private String password;//用户密码

注意:如果User类中有其它封装的对象,要在这个对象上添加@Valid注解来开启嵌套验证,才能让这个嵌套对象里面的@NotEmpty等内部注解生效,这个特性支持层层嵌套。

3.在接收参数时使用@Valid启用验证

比如在Controller层接收前端发送的用户名和密码,用User类去接收RequestBody的内容,添加@Valid注解开启验证功能。这里也可以用@Validated,两者的区别是:

  • @Valid是java自带的,@Validated是spring提供的,后者额外支持了一个group特性
  • @Valid可以用在方法、方法参数、成员属性(支持嵌套验证)
  • @Validatted可以用在类、方法、方法参数,因为不能用在成员属性上,所以不支持嵌套验证,但可以配合@Valid使用实现。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/user")
public class UserController {
    UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/register")
    public ResponseBean submitRegister(@RequestBody @Valid User user) {

        userService.addUser(userDO);
        return ResponseBean.ok("注册成功");
    }
}

4.处理异常

如果数据验证没有问题,那么程序照常运行。如果验证不通过,则会抛出异常MethodArgumentNotValidException,可以在@ControllerAdvice中进行全局捕捉异常。e.getBindingResult().getFieldError().getDefaultMessage()会输出定义约束时的message信息,如上面的“用户名不能为空”。

1
2
3
4
5
6
7
8
@RestControllerAdvice
public class GlobalExceptionAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseBean handleArgumentInvalid(MethodArgumentNotValidException e){
        return ResponseBean.error(4001,"数据格式不符合要求:"+e.getBindingResult().getFieldError().getDefaultMessage());
    }
}

5.补充说明

上面的例子用一个User类来接收前端的数据,用@Valid来开启验证,返回的是MethodArgumentNodValidException,有时候,我们只对前端发送的一个参数进行直接验证,比如前端发送了一个用户名来查询一些信息,这时候可以直接对这个参数进行验证,但需要在类上添加@Validated注解开启验证,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
    UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/address/{id}")
    public ResponseBean getAddress(@PathVariable("username") @NotEmpty int id){
        String address=userService.getBirthdayById(id);
        return ResponseBean.ok(address);
    }

    @GetMapping("/address")
    public ResponseBean getAddress2(@Param("username") @NotEmpty int id){
        String address=userService.getBirthdayById(id);
        return ResponseBean.ok(address);
    }
}

此时如果验证失败,则抛出ConstraintViolationException,需要进行相应的异常处理。