前言:
Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。

引入依赖pom.xml

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

常用注解

注意不用错了类型!

注解

限制

@Null

限制只能为null

@NotNull

限制必须不为null

@AssertFalse

限制必须为false

@AssertTrue

限制必须为true

@DecimalMax(value)

限制必须为一个不大于指定值的数字

@DecimalMin(value)

限制必须为一个不小于指定值的数字

@Digits(integer,fraction)

限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction

@Future

限制必须是一个将来的日期

@Max(value)

限制必须为一个不大于指定值的数字

@Min(value)

限制必须为一个不小于指定值的数字

@Past

限制必须是一个过去的日期

@Pattern(value)

限制必须符合指定的正则表达式 字符串

@Size(max,min)

限制字符长度必须在min到max之间 【不是数字】

@Past

验证注解的元素值(日期类型)比当前时间早

@NotEmpty

验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)

@NotBlank

验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格

@Email

验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

@Length(min = 6, max = 16)

指定传入的字符串的长度

使用

对于web服务来说,为防止非法参数对业务造成影响,在Controller层一定要做参数校验的!大部分情况下,请求参数分为如下两种形式:

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

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

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

首先在实体类里面加入校验的注解

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User implements Serializable {

    private static final long serialVersionUID = -5577261004651495987L;

    @TableId(type = IdType.AUTO)
    private Integer userId;


    @NotEmpty
    @Length(min = 6, max = 16, message = "密码不能为空") //@Length本身就不允许为空
    private String password;
    
    private Integer role;

    private String salt;
    /**
     * 通过邮箱来登录
     */
    @Email(regexp = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$")
    @NotNull
    private String email;

    private Date registerTime;

    @Override
    public String toString() {
        String ret = null;
        ObjectMapper mapper = new ObjectMapper();
        try {
            ret = mapper.writeValueAsString(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret;
    }
}

然后在接收User对象的时候加入@Valid或者@Validated注解:

注意:
spring提供的验证:org.springframework.validation.annotation.Validated;
javax提供的验证:javax.validation.Valid;
@Valid 和 @Validated 都用来触发一次校验, @Valid 是 JSR 303规范的注解, @Validated 是Spring 加强版的注解, 加强的地方有:
@Validated 支持组序列, 该特性用的较少 推荐使用 @Validated 注解.

有几种方法都可以捕获到出现的不匹配情况:

方法一:全局异常捕获
@PostMapping("/login")
 public RetJson<Object> login(@Valid @RequestBody User user){
	...
 }

使用全局异常返回错误信息(就是写在实体类上面的message信息)

@RestControllerAdvice
@Slf4j
public class ExceptionResolver {
	/**
     * 参数不匹配异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = { MethodArgumentNotValidException.class})
    public RetJson methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        log.error("------->MethodArgumentNotValidException参数异常-------- ", e);
        return RetJson.fail(ResultCode.VALIDATE_FAILED.getCode(), "参数异常"+e.getBindingResult().getFieldError().getDefaultMessage());
        //getDefaultMessage()会返回message信息
    }
}

第二种:在方法上直接处理

在实体类传参时加上BindingResult注解:

同时注意BindingResult必须紧跟@ModelAttribute的后面
例如:必须写成这种方式:
public String add(@Valid RoleBO role, BindingResult binding,
Map<String, Object> request)

写成如下方式,
public String add(@Valid RoleBO role, Map<String, Object> request),BindingResult result)
会抛出异常:java.lang.IllegalStateException: An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the @RequestBody or the @RequestPart arguments to which they apply:

其实过程就是:

  1. 在 Pojo 类的字段上, 加上 Hibernate validator 注解
  2. 在Controller 函数的形参前加上 @Valid 或 @Validated 注解, 触发一次validation.
  3. 在每个 @Valid 或 @Validated 注解参数后, 紧跟一个 BindingResult 类型的参数. 如果没有提供对应的 BindingResult 参数, Spring MVC 将抛出异常.
  4. 在Controller 函数中, 通过 BindingResult 类型的参数获取检验的结果.
@PostMapping("/login")
 public RetJson<Object> login(@Valid @RequestBody User user, BindingResult result){
	if (result.hasErrors()) {
          log.error("[login]参数错误:User={}", user);
           String s = result.getFieldError().getDefaultMessage();
           throw new BaseException(ResultCode.VALIDATE_FAILED.getCode(), s);
     }
        ...
 }

这样也可以直接返回message错误的信息。

方法三:工具类

写一个工具类判断有没有不匹配的情况:

public class ValidatedUtil {
    private static Validator validator=
            Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();

    public static<T> Boolean validate(T v){
        Set<ConstraintViolation<T>> set= validator.validate(v);
        return set.size() == 0;
    }

 	public static void validData(BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            StringBuilder sb = new StringBuilder();
            for (ObjectError error : bindingResult.getAllErrors()) {
                sb.append(error.getDefaultMessage());
            }
            throw new BaseException(ResultCode.VALIDATE_FAILED.getCode(), sb.toString());
        }
    }
}
当然单个参数也可以直接使用
@GetMapping("/getEmailCode")
	public RetJson<Object> getEmailCode(
			@RequestParam("email") @Valid 
			@Email(regexp = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$", message = "邮箱格式不对") String email) {
		//使用全局异常拦截MethodArgumentNotValidException 可以拦截到这个信息	
}