本篇文章介绍如何在springBoot中使用Hibernate Validate,如果优美的通过springBoot全局异常处理Hibernate Validate在参数校验失败后报出的异常
导入依赖
如果是springBoot项目,那么spring-boot-starter-web中就已经依赖hibernate-validator了
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
注意springboot2.x后导入依赖需要手动
<!--参数校验:在springboot2.x之后 web起步依赖中不再含有该依赖 需要手动在引入依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
Validator的configuration(不是必须的)
由于默认情况下,Hibernate-validator使用的校验策略是依次校验,并且将不通过的结果保存,最后再统一抛出异常信息,但实际上,当校验出现第一个不满足情况的时候,就可以停止了(当然,如果选择全部验证完也是可以的),所以我们手动配置一下
配置的目的就是当第一个参数不匹配的时候就直接停止下面检验,直接返回
/*
Hibernate Validator的校验模式有两种情况:
1.快速失败(验证失败一个,快速返回错误信息)
2.普通模式(默认,会校验完所有的属性,然后返回所有的验证失败信息)
我们一般使用第一种,也就是校验时候验证失败的时候就及时返回,SpringBoot需要开启配置*/
//hibernate validator快速失败模式
@Configuration
public class Jsr303Config {
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation
.byProvider( HibernateValidator.class )
.configure()
.failFast( true )// 将fail_fast设置为true即可,如果想验证全部,则设置为false或者取消配置即可
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
由上很简单的已经在springBoot中配置了 Hibernate Validate
案例使用
1.首先我们建立VO对象
public class UserVo {
@NotBlank(message = "用户名不能为空")
private String name;
@Max(value = 120, message = "年龄不能超过120岁")
private int age;
@NotNull
@Size(min = 8, max = 20, message = "密码必须大于8位并且小于20位")
private String password;
@Email(message = "请输入符合格式的邮箱")
private String email;
}
2.Controller类编写
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public User addUser(@Valid @RequestBody User user) {
// 仅测试验证过程,省略其他的逻辑
return user;
}
}
注意:
@Valid注解 它和 @Validated 作用都是:通过注解能够使得验证生效,如果去除的话,可以看到验证逻辑并没有生效。@Validated是spring框架的注解,两者更 多区别网上都有很好的解释
3.检验失败报异常
通过上面的一个简单注解之后,验证的逻辑已经能够生效,然而,在测试的时候,可能会出现下面的情况
参数前加上@Valid或Spring的 @Validated注解,如果验证不通过会抛出BindException异常,
如果参数前有@RequestBody注解,验证错误会抛出MethodArgumentNotValidException异常,
并变成400(BAD_REQUEST)响应
{
"timestamp": "2018-11-09T01:47:56.985+0000",
"status": 400,
"error": "Bad Request",
"errors": []
}
/**
* 验证不通过抛出 `MethodArgumentNotValidException`
*/
@PostMapping("/user3")
public R handle3(@Valid @RequestBody User user) {
// ...
return R.success();
}
/**
* 验证不通过抛出 `BindException`
*/
@PostMapping("/user4")
public R handle4(@Valid User user) {
// ...
return R.success();
}
以上是@RequestBody标注的参数的验证及错误异常
but
@PathVariable以及@RequestParam标注的入参(不生效),而事实上,这两种类型的操作也是非常常用的(也是需要对这两种类型进行验证,除了手动验证外,还有一种通用的解决方案,也是通过注解来实现)
@RestController
@RequestMapping("/users")
public class UserController {
// .....
@GetMapping("/{name}")
public User getUserByName(
@NotNull
@Size(min = 1, max = 20, message = "用户名格式有误")
@PathVariable String name) {
User user = new User();
user.setName(name);
return user;
}
@GetMapping
public User getUserByNameParam(
@NotNull
@Size(min = 1, max = 20, message = "用户名格式有误")
@RequestParam("name") String name) {
User user = new User();
user.setName(name);
return user;
}
}
为了让对应的注解生效,可以在类的上方使用@Validated进行标注,注意是标注在类上方,即
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
// ...
}
但此时如果验证失败,会抛出异常信息,而且,异常类型不是MethodArgumentNotValidException,而是ConstraintViolationException
所以总结一次:
1.参数前加上@Valid或Spring的 @Validated注解,如果验证不通过会抛出BindException异常,
2.如果参数前有@RequestBody注解,验证错误会抛出MethodArgumentNotValidException异常,
3.如果@PathVariable以及@RequestParam标注的入参在检验错误报ConstraintViolationException异常
后续都得对这些错误进行全局处理
4.手动处理检验错误
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public User addUser(@Valid @RequestBody User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 具体的处理逻辑,如封装错误信息等
}
return user;
}
}
但是这种方式不是很优雅,因为对于每一个需要验证的方法,都需要进行这样的逻辑(虽然封装处理可以解决,但依旧每次需要手动调用以及加入BindingResult参数)
5.全局处理3种异常
配合我以前文《springBoot全局异常》文章
/**
* 拦截表单参数校验
* 处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler({BindException.class})
public ResultVo bindException(BindException e) {
BindingResult bindingResult = e.getBindingResult();
log.info("数据验证异常:{}", bindingResult.getFieldError().getDefaultMessage());
return ResultVoUtil.error(2001,Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
}
/**
* 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(ConstraintViolationException.class)
public ResultVo ConstraintViolationExceptionHandler(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
ConstraintViolation<?> violation = violations.iterator().next();
String message = violation.getMessage();
log.info("数据验证异常:{}", message);
return ResultVoUtil.error(2001,message);
}
/**
* 拦截JSON参数校验
* 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVo bindException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
log.info("数据验证异常:{}", bindingResult.getFieldError().getDefaultMessage());
return ResultVoUtil.error(2001,Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
}
6.测试
postman测试:

控制台输出:
