SpringBoot的参数校验之Spring Validation

前言

Java API规范 (JSR303) 定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。接下来,我们以spring-boot项目为例,介绍Spring Validation的使用。几乎涵盖你需要的SpringBoot所有操作

引入依赖

方式一:

  
    org.hibernate  
    hibernate-validator  
    6.2.3.Final  

方式二:
(本文采用此方式)


    org.springframework.boot
    spring-boot-starter-validation

实战部分

此文需要仍需引入web的依赖


    org.springframework.boot
    spring-boot-starter-web

在web服务里通常会通过以下两种形式进行传参:

  1. POSTPUT请求,使用requestBody传递参数;
  2. GET请求,使用requestParam/PathVariable传递参数。

requestBody参数校验

POST、PUT请求一般会使用requestBody传递参数,这种情况下,后端使用 DTO 对象进行接收。只要给 DTO 对象加上@Validated注解就能实现自动参数校验。比如,有一个保存User的接口,要求userName长度是2-10,account和password字段长度是6-20。如果校验失败,会抛出MethodArgumentNotValidException异常,Spring默认会将其转为400(Bad Request)请求。

DTO 表示数据传输对象(Data Transfer Object),用于服务器和客户端之间交互传输使用的。在 spring-web 项目中可以表示用于接收请求参数的Bean对象。

  • DTO字段上声明约束注解

    import org.hibernate.validator.constraints.Length;
    
    import javax.validation.constraints.NotNull;
    
    public class UserDTO {
      private Long userId;
    
      @NotNull
      @Length(min = 2,max = 10)
      private String userName;
    
      @NotNull
      @Length(min = 6,max = 20)
      private String account;
    
      @NotNull
      @Length(min = 6,max = 20)
      private String password;
    
      public Long getUserId() {
          return userId;
      }
    
      public void setUserId(Long userId) {
          this.userId = userId;
      }
    
      public String getUserName() {
          return userName;
      }
    
      public void setUserName(String userName) {
          this.userName = userName;
      }
    
      public String getAccount() {
          return account;
      }
    
      public void setAccount(String account) {
          this.account = account;
      }
    
      public String getPassword() {
          return password;
      }
    
      public void setPassword(String password) {
          this.password = password;
      }
    }
  • 在方法参数上声明校验注解
    这种情况下,使用@Valid和@Validated都可以。

    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    public class UserController {
    
      @PostMapping("/save")
      public Map saveUser(@RequestBody @Validated UserDTO userDTO){
          Map res = new HashMap();
          res.put("code","200");
          res.put("message","ok");
          return res;
      }
    }

    requestParam/PathVariable参数校验

    GET请求一般会使用requestParam/PathVariable传参。如果参数比较多 (比如超过 6 个),还是推荐使用DTO对象接收。否则,推荐将一个个参数平铺到方法入参中。在这种情况下,必须在Controller类上标注@Validated注解,并在入参上声明约束注解 (如@Min等)。如果校验失败,会抛出ConstraintViolationException异常。代码示例如下:

    @RestController
    @Validated
    public class SelectController {
    
      @GetMapping("/{userId}")
      public Map detail(@PathVariable("userId") @Min(1000000000L) Long userId){
          Map res = new HashMap();
          res.put("code","200");
          res.put("message","ok");
          return res;
      }
    
      @GetMapping("/getByAccount")
      public Map getByAccount(@Length(min = 6,max = 20)@NotNull String account){
          Map res = new HashMap();
          res.put("code","200");
          res.put("message","ok");
          return res;
      }
    }

    统一异常处理

    校验失败会抛出MethodArgumentNotValidException或者ConstraintViolationException异常,我们需要通过统一异常处理来返回一个更友好的报错。

    import org.springframework.http.HttpStatus;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import javax.validation.ConstraintViolationException;
    import java.util.HashMap;
    import java.util.Map;
    
    @RestControllerAdvice
    public class CommonExceptionHandler {
    
      @ExceptionHandler({MethodArgumentNotValidException.class})
      @ResponseStatus(HttpStatus.OK)
      public Map handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
          BindingResult bindingResult = ex.getBindingResult();
          StringBuilder sb = new StringBuilder("校验失败:");
          for (FieldError fieldError : bindingResult.getFieldErrors()) {
              sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
          }
          String msg = sb.toString();
          Map err = new HashMap<>();
          err.put("code","-200");
          err.put("message",msg);
          return err;
      }
    
      @ExceptionHandler({ConstraintViolationException.class})
      @ResponseStatus(HttpStatus.OK)
      public Map handleConstraintViolationException(ConstraintViolationException ex) {
          Map err = new HashMap<>();
          err.put("code","-200");
          err.put("message",ex.getMessage());
          return err;
      }
    
    }
    

    进阶使用

    分组校验

    在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。还是上面的例子,比如保存User的时候,UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;其它字段的校验规则在两种情况下一样。这个时候使用分组校验的代码示例如下:

  • 约束注解上声明适用的分组信息groups

    public class UserDTO {  
    
      @Min(value = 10000000000000000L, groups = Update.class)  
      private Long userId;  
    
      @NotNull(groups = {Save.class, Update.class})  
      @Length(min = 2, max = 10, groups = {Save.class, Update.class})  
      private String userName;  
    
      @NotNull(groups = {Save.class, Update.class})  
      @Length(min = 6, max = 20, groups = {Save.class, Update.class})  
      private String account;  
    
      @NotNull(groups = {Save.class, Update.class})  
      @Length(min = 6, max = 20, groups = {Save.class, Update.class})  
      private String password;  
    
      public interface Save {  
      }  
    
      public interface Update {  
      }  
    }

    @Validated注解上指定校验分组

    @PostMapping("/save")  
    public Map saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {  
            Map res = new HashMap();
          res.put("code","200");
          res.put("message","ok");
          return res; 
    }  
    
    @PostMapping("/update")  
    public Map updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {  
           Map res = new HashMap();
          res.put("code","200");
          res.put("message","ok");
          return res;
    }

你可能感兴趣的:(java)