乐趣区

关于java:SpringBoot的参数校验之Spring-Validation

前言

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

引入依赖

形式一:

<dependency>  
    <groupId>org.hibernate</groupId>  
    <artifactId>hibernate-validator</artifactId>  
    <version>6.2.3.Final</version>  
</dependency>

形式二:
(本文采纳此形式)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

实战局部

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

在 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<String,Object> saveUser(@RequestBody @Validated UserDTO userDTO){Map<String,Object> 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<String,Object> detail(@PathVariable("userId") @Min(1000000000L) Long userId){Map<String,Object> res = new HashMap();
          res.put("code","200");
          res.put("message","ok");
          return res;
      }
    
      @GetMapping("/getByAccount")
      public Map<String,Object> getByAccount(@Length(min = 6,max = 20)@NotNull String account){Map<String,Object> 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<String,Object> 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<String,Object> err = new HashMap<>();
          err.put("code","-200");
          err.put("message",msg);
          return err;
      }
    
      @ExceptionHandler({ConstraintViolationException.class})
      @ResponseStatus(HttpStatus.OK)
      public Map<String,Object> handleConstraintViolationException(ConstraintViolationException ex) {Map<String,Object> 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<String,Object> saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {Map<String,Object> res = new HashMap();
          res.put("code","200");
          res.put("message","ok");
          return res; 
    }  
    
    @PostMapping("/update")  
    public Map<String,Object> updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {Map<String,Object> res = new HashMap();
          res.put("code","200");
          res.put("message","ok");
          return res;
    }
退出移动版