安装使用

在springboot 2.3.x以前的版本中,spring-boot-start-web中会将其默认引入,之后的版本将其剔除,需要手动引入下面的依赖

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

可用的注解

常用来使用的注解包括以下这些:

javax包里的注解

也就是javax.validation.constraints包里的注解

  • @Vaild可以加在方法上、属性上、参数前、构造器上、变量类型前,表示这个地方需要校验

  • @NotNull加在方法、属性、参数、构造器、变量类型、注解上,校验必须不为null,支持任何类型

  • @NotBlank加在方法、属性、参数、构造器、变量类型、注解上,校验必须不为null或者空值,支持CharSequence类型

  • @NotEmpty加在方法、属性、参数、构造器、变量类型、注解上,校验必须不为空值,支持CharSequence,Collection,Map,Array类型

  • @Null加在方法、属性、参数、构造器、变量类型、注解上,校验必须为null,支持CharSequence,Collection,Map,Array类型

  • @Digits加在方法、属性、参数、构造器、变量类型、注解上,校验不为空,支持BigDecimal,BigInteger,CharSequence,byte, short, int, long以及它们的包装类

  • @Email加在方法、属性、参数、构造器、变量类型、注解上,校验必须不为null或者空值,支持CharSequence类型

  • @Future加在方法、属性、参数、构造器、变量类型、注解上,校验时间必须是未来时间,支持以下类型时间

    • java.util.Date
    • java.util.Calendar
    • java.time.Instant
    • java.time.LocalDate
    • java.time.LocalDateTime
    • java.time.LocalTime
    • java.time.MonthDay
    • java.time.OffsetDateTime
    • java.time.OffsetTime
    • java.time.Year
    • java.time.YearMonth
    • java.time.ZonedDateTime
    • java.time.chrono.HijrahDate
    • java.time.chrono.JapaneseDate
    • java.time.chrono.MinguoDate
    • java.time.chrono.ThaiBuddhistDate
  • @FutureOrPresent加在方法、属性、参数、构造器、变量类型、注解上,校验时间必须是未来时间或者现在,支持时间类型同@Future

  • @Past加在方法、属性、参数、构造器、变量类型、注解上,校验时间必须是过去时间,支持时间类型同@Future

  • @PastOrPresent加在方法、属性、参数、构造器、变量类型、注解上,校验时间必须是过去时间或者现在,支持时间类型同@Future

  • @Max加在方法、属性、参数、构造器、变量类型、注解上,校验必须为数字且最大为设置的值,支持类型同@Digits

  • @Min加在方法、属性、参数、构造器、变量类型、注解上,校验必须为数字且最小为设置的值,支持类型同@Digits

  • @Size加在方法、属性、参数、构造器、变量类型、注解上,校验元素大小必须在设置的最小值与最大值之间,包含边界值,至持CharSequence,Collection,Map,Array类型

  • @DecimalMax@Max类似,值是一个符合BigDecimal规则的字符串

  • @DecimalMin@Min类似,值是一个符合BigDecimal规则的字符串

  • @AssertTrue加在方法、属性、参数、构造器、变量类型、注解上,校验元素必须true,支持booleanBoolean类型

  • @AssertFalse加在方法、属性、参数、构造器、变量类型、注解上,校验元素必须false,支持booleanBoolean类型

  • @Negative加在方法、属性、参数、构造器、变量类型、注解上,校验数字必须是一个负数,不包含0,支持类型同@Digits

  • @NegativeOrZero加在方法、属性、参数、构造器、变量类型、注解上,校验数字必须是一个负数,包含0,支持类型同@Digits

  • @Positive加在方法、属性、参数、构造器、变量类型、注解上,校验数字必须是一个正数,不包含0,支持类型同@Digits

  • @PositiveOrZero加在方法、属性、参数、构造器、变量类型、注解上,校验数字必须是一个正数,包含0,支持类型同@Digits

  • @Pattern加在方法、属性、参数、构造器、变量类型、注解上,校验字符必须匹配设置的正则表达式,支持CharSequence类型

  • @List一个特殊的注解,可以用来与上述注解一起使用,用来为这个元素定义多组规则。

spring里的常用注解

  • @Validated@Valid,不同的是@Validated支持分组,而@Valid不支持分组,相当于是@Valid的增强版

hibernate里的常用注解

  • @Length加在方法、属性、参数、构造器、变量类型、注解上,校验字符串的长度不能超过设置的值,支持String类型
  • @ISBN加在方法、属性、参数、构造器、变量类型、注解上,校验字符必须是一个标准的ISBN图书编号,支持CharSequence类型
  • @Range加在方法、属性、参数、构造器、变量类型、注解上,校验数字必须在设置的最小值与最大值之间,包含边界值,支持所有的数字类型
  • @URL加在方法、属性、参数、构造器、变量类型、注解上,校验字符串必须是一个URL,协议包含http,https,file,ftp,jarjava.net.URL支持的协议
  • @EAN加在方法、属性、参数、构造器、变量类型、注解上,校验字符必须是一个国际商品编码,支持CharSequence类型
  • CreditCardNumber加在方法、属性、参数、构造器、变量类型、注解上,校验字符串必须是一个信用卡号的格式

validation使用方式

参数绑定

@Slf4j
@RestController
public class DemoController {

    @RequestMapping("/test")
    public String test(@Validated Foo foo, BindingResult bindingResult) {
        if(bindingResult.hasErrors()){
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                String fieldName = fieldError == null ? "" : fieldError.getField();
                String defaultMessage = fieldError == null ? "" : fieldError.getDefaultMessage();
                String errorMsg = "[" + fieldName + "]" + defaultMessage;
                log.info("捕捉到参数校验异常: {}", errorMsg);
                //...
            }
            return "fail";
        }
        return "success";
    }
}

异常捕获

@Slf4j
@RestController
public class DemoController {

    @RequestMapping("/test")
    public String test(@Validated Foo foo) {
        // ...
        return "success";
    }
}
@Slf4j
@RestControllerAdvice
public class ExceptionHandle {
  
    /**
     * 处理validation框架中的{@link MethodArgumentNotValidException}异常
     * <p>该异常一般出在用{@code @RequestBody}注解标记的参数校验,在对象中的参数有问题是抛出</p>
     *
     * @param e {@link MethodArgumentNotValidException}
     * @return string
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String doNoValidExceptionHandler(MethodArgumentNotValidException e) {
        // 默认捕获第一个不符合校验规则的错误信息
        return validationExceptionHandler(e.getBindingResult(), e.getMessage());
    }

    /**
     * 处理validation框架中的{@link ConstraintDeclarationException}异常
     * <p>该异常一般出在用{@code @RequestParam}注解标记的参数校验</p>
     *
     * @param e {@link ConstraintDeclarationException}
     * @return string
     */
    @ExceptionHandler(ConstraintDeclarationException.class)
    public String doNoValidExceptionHandler(ConstraintDeclarationException e) {
        String errorMsg = e.getMessage();
        log.info("捕捉到参数校验异常: [{}]", errorMsg, e);
        return errorMsg;
    }

    /**
     * 处理validation框架中的{@link BindException}异常
     * <p>该异常一般出在用{@code @RequestBody}注解标记的参数校验,在对象嵌套类型参数有问题时抛出</p>
     *
     * @param e {@link BindException}
     * @return string
     */
    @ExceptionHandler(BindException.class)
    public String doNoValidExceptionHandler(BindException e) {
        return validationExceptionHandler(e.getBindingResult(), e.getMessage());
    }

    /**
     * 处理validation框架的异常
     *
     * @param bindingResult {@link BindingResult} 异常绑定的对象信息
     * @param message       异常信息
     * @return string
     */
    private String validationExceptionHandler(BindingResult bindingResult, String message) {
        // 一般只捕获第一个不符合校验规则的错误信息即可,与配置的快速失败搭配更好
        // 错误字段对象
        FieldError fieldError = bindingResult.getFieldError();
        // 错误字段名
        String fieldName = fieldError == null ? "" : fieldError.getField();
        // 具体错误信息
        String defaultMessage = fieldError == null ? "" : fieldError.getDefaultMessage();
        String errorMsg = "[" + fieldName + "]" + defaultMessage;
        log.info("捕捉到参数校验异常: [{}]", message);
        return errorMsg;
    }
}

ConstraintDeclarationException类型异常出现的方式

@Slf4j
@Validated
@RestController
public class DemoController {

    @RequestMapping("/test")
    public String test(@RequestParam("foo") @NotBlank(message = "foo不可为空") String foo) {
        // ...
        return "success";
    }
}

这种校验方式会抛出ConstraintDeclarationException异常,并且如果使用校验结果绑定的方式,并不会获取到错误的信息,只能通过异常捕获的方式处理。而且还需要在类上加@Validated,如果不加这种简单参数校验将不会有任何作用。

配置快速失败

@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

分组校验

分组校验需要定义一个空接口,然后将这个接口设置到校验的graoups里和@Validate里即可。

public class Foo {
  	@NotNull(message="姓名不能为null", groups={Test.class})
  	@NotBlank(message="姓名不能为空字符串", groups={TestBlank.class})
		private String name;
  
  	public interface Test {}
  	public interface TestBlank {}
}
@Slf4j
@RestController
public class DemoController {

    @RequestMapping("/test")
    public String test(@Validated(Foo.Test.class) Foo foo) {
        // ...
        return "success";
    }

  	@RequestMapping("/test/blank")
    public String test(@Validated(Foo.TestBlank.class) Foo foo) {
        // ...
        return "success";
    }
}

嵌套校验

嵌套校验就是需要对一个对象里的对象属性进行嵌套校验,具体如下

public class Foo {
  	@NotNull(message="姓名不能为null", groups={Test.class})
  	@NotBlank(message="姓名不能为空字符串", groups={TestBlank.class})
		private String name;
  	
  	@Valid// 在需要嵌套校验的字段上加入@Valid或者@Vaildated
  	@NotNull(message="inner不可为null")
  	private InnerFoo inner;
  
  	public interface Test {}
  	public interface TestBlank {}
}


public class InnerFoo {
  	@NotBlank(message="手机号不能为空")
		private String mobile;
}