参数校验框架之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.Datejava.util.Calendarjava.time.Instantjava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimejava.time.MonthDayjava.time.OffsetDateTimejava.time.OffsetTimejava.time.Yearjava.time.YearMonthjava.time.ZonedDateTimejava.time.chrono.HijrahDatejava.time.chrono.JapaneseDatejava.time.chrono.MinguoDatejava.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;
}