简介
Spring Validation 是在Spring Context下的,在Spring Boot项目中,我们引入spring-boot-starter-web便会引入进来,Spring Validation是对Hibernate Validator的二次封装,使我们可以更方便的在Spring MVC中完成自动校验。
Hibernate Validator是对JSR-303(Bean Validation)的参考实现。Hibernate Validator 提供了JSR-303规范中所有内置constraint的实现,除此之外还有一些附加的constraint。
JSR-303定义的constraint:
Constraint | Description |
---|---|
@Null | 被注解的元素必须为null |
@NotNull | 被注解的元素必须不为null |
@AssertTure | 被注解的元素必须为ture |
@AssertFalse | 被注解的元素必须为false |
@Min(value) | 被注解的元素必须是数字且必须大于等于指定值 |
@Max(value) | 被注解的元素必须是数字且必须小于等于指定值 |
@DecimalMin(value) | 被注解的元素必须是数字且必须大于等于指定值 |
@DecimalMax(value) | 被注解的元素必须是数字且必须小于等于指定值 |
@Size(max, min) | 被注解的元素必须在指定的范围内 |
@Digits(integer, fraction) | 被注解的元素必须是数字且其值必须在给定的范围内 |
@Past | 被注解的元素必须是一个过去的日期 |
@Future | 被注解的元素必须是一个将来的日期 |
@Pattern(value) | 被注解的元素必须符合给定正则表达式 |
Hibernate Validator附加实现的constraint
Constraint | Description |
---|---|
被注解的元素必须是Email地址 | |
@Length(min, max) | 被注解的元素长度必须在指定的范围内 |
@NotEmpty | 被注解的元素必须 |
@Range | 被注解的元素(可以是数字或者表示数字的字符串)必须在给定的范围内 |
@URL | 被注解的元素必须是URL |
当然,我们也可以自定义实现,自定义实现在下面使用中在讲吧。
使用
在开始使用之前,先做好准备工作,创建一个Spring Boot项目,然后引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
只需要引入这个依赖就可以了。
使用@Validated注解拦截校验
在Controller中,我们需要校验前端传递过来的参数,我们可以这么写
@RestController
public class TestController {
@PostMapping("/test")
public Object test(@RequestBody @Validated User user, BindingResult result) {
if (result.hasErrors()) {
return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
}
return user;
}
}
只需要在需要校验的实体前面打上@Validated注解就可以了,这时候,如果我们传递的参数符合要求,则会正常返回。否则返回:
[
"age字段不合法",
"name字段不合法"
]
它会将我们所有不合法信息一次性全部返回,在日常开发中,我们可以吧校验BindingResult是否有错误信息的校验统一抽出到一个工具类中去做处理,使用项目中统一格式返回错误信息就好。这就是一个最简单的校验示例了,其他注解也都是类似的,就不多举例了,可以自己尝试着玩玩。
在日常开发中想必都曾遇到过这样的需求,比如这个age这个字段,我想要这个字段只在PC端校验,在App端不做限制,这就需要用到分组校验了,每个注解都提供了一个group属性,利用这个属性就可以轻易做到以上需求。比如在User上的注解中加入group属性,指定其被校验的group:
public class User {
@Length(min = 1, max = 22, message = "name字段不合法", groups = {App.class, PC.class})
private String name;
@Min(value = 1, message = "age字段不合法", groups = PC.class)
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在Controller中的@Validated中指定当前group:
@RestController
public class TestController {
@PostMapping("/test")
public Object test(@RequestBody @Validated(App.class) User user, BindingResult result) {
if (result.hasErrors()) {
return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
}
return user;
}
}
这时候我再使用两个不合法字段访问返回:
[
"name字段不合法"
]
可以看到,它并没有对age字段进行校验。这就是它的分组校验。
@ControllerAdvice
@RestController
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BindException.class)
public Object validExceptionHandler(BindException e){
FieldError fieldError = e.getBindingResult().getFieldError();
assert fieldError != null;
log.error(fieldError.getField() + ":" + fieldError.getDefaultMessage());
// 将错误的参数的详细信息封装到统一的返回实体
return CreditQueryResp.failure(NormalExceptionEnum.PARAM_EXCEPTION.getCode(),fieldError.getDefaultMessage()) ;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object validExceptionHandler(MethodArgumentNotValidException e){
FieldError fieldError = e.getBindingResult().getFieldError();
assert fieldError != null;
log.error(fieldError.getField() + ":" + fieldError.getDefaultMessage());
return CreditQueryResp.failure(NormalExceptionEnum.PARAM_EXCEPTION.getCode(),fieldError.getDefaultMessage()) ;
}
@ExceptionHandler(Throwable.class)
CreditQueryResp handleException(Throwable throwable){
if(throwable instanceof NullPointerException ){
return CreditQueryResp.failure(NormalExceptionEnum.PARAM_EXCEPTION.getCode(),throwable.getMessage());
}if(throwable instanceof BusinessException){
BusinessException businessException=(BusinessException)throwable;
return CreditQueryResp.failure(businessException.getBizCode().getCode(),businessException.getMessage()) ;
}
log.error("异常信息:{}",throwable);
return CreditQueryResp.failure(NormalExceptionEnum.SYSTEM_EXCEPTION.getCode(), NormalExceptionEnum.SYSTEM_EXCEPTION.getMessage()) ;
}
}
@Component
public class ValidatorUtil {
private static Validator validator;
private static SmartValidator validatorAdapter;
static {
// 快速返回模式
validator = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory()
.getValidator();
validatorAdapter=getValidatorAdapter(validator);
}
public static Validator getValidator() {
return validator;
}
private static SmartValidator getValidatorAdapter(Validator validator) {
if (validatorAdapter == null) {
validatorAdapter = new SpringValidatorAdapter(validator);
}
return validatorAdapter;
}
public Object validator(Person person){
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
validatorAdapter.validate(person, result);
if (result.hasErrors()) {
return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
}
return person;
}
}
controller代码
@RequestMapping("/hello")
public String sayHello(@RequestBody Person person){
validator.validator(person);
return "success";
}
entity代码:
@Data
public class Person {
@NotNull(message = "姓名不能为空" )
private String name ;
@Max(value = 18,message = "年龄不能超过18岁")@Min(value = 10,message = "年龄不能小于10岁")
private Integer age ;
private String format ;
}
调用这个工具类,就可以在方法中验证对象的属性了
总结
项目中往往有很多地方需要用到空判断 , 一般会有全局异常拦截类,所以一般就不会用BindingResult 去接受错误的参数.group区分入口 ,也可以方法中对对象进行判断,应该在项目的check上面应该足够了