参数校验框架之spring-vaildation
安装使用
在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,支持boolean和Boolean类型
- 
@AssertFalse加在方法、属性、参数、构造器、变量类型、注解上,校验元素必须false,支持boolean和Boolean类型
- 
@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,- jar等- java.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;
}