SpringBoot环境下Hibernate Validator参数校验

参数校验

在开发中经常需要做字段的参数校验,一般是长度,非空判断,以及一些手机号码和邮箱号的格式验证,而这些代码大多又是重复劳动且与业务逻辑没有太大关系

hibernate validator 提供了一套比较比较方便的参数校验实现方式

首先,先添加参数校验依赖

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

注:spring-boot-starter-web中已经添加了,引入了就不要再引入了

参数校验简单实现

先简单实现一个注册的参数校验的demo

  1. controller层(参数UserVO前面需要添加注解@Validated或者@Valid

    @RestController
    public class ParamValidController {
    
    	/**
    	 * @param userVO
    	 * @return
    	 */
    	@PostMapping("/register")
    	public String register(@RequestBody @Validated UserVO userVO) {
    		return "注册成功";
    	}
    }
    
  2. 实体类

    @Data
    public class UserVO {
    	@NotBlank(message = "用户名不能为空")
    	String username;
    	@NotBlank(message = "密码不能为空")
    	String password;
    	@NotNull(message = "性别不能为空")
    	@Range(min = 0, max = 1, message = "性别不合法")
    	Integer sex;
    	@Range(min = 18, message = "年龄不能小于18")
    	@NotNull(message = "年龄不能为空")
    	Integer age;
    }
    
  3. 使用postman工具发送错误格式json进行测试

{
    "username":"",
    "password":"",
    "sex":0,
    "age":16
}

控制台会打印拦截信息(证明参数有误,校验失败)

Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.fisher.valid.controller.ParamValidController.register(com.fisher.valid.vo.UserVO) with 3 errors: [Field error in object 'userVO' on field 'username': rejected value []; codes [NotBlank.userVO.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userVO.username,username]; arguments []; default message [username]]; default message [用户名不能为空]] [Field error in object 'userVO' on field 'age': rejected value [16]; codes [Range.userVO.age,Range.age,Range.java.lang.Integer,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userVO.age,age]; arguments []; default message [age],9223372036854775807,18]; default message [年龄不能小于18]] [Field error in object 'userVO' on field 'password': rejected value []; codes [NotBlank.userVO.password,NotBlank.password,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userVO.password,password]; arguments []; default message [password]]; default message [密码不能为空]] ]

从上面控制台打印的错误信息中可以得出抛出了MethodArgumentNotValidException异常,在开发中我们不可能返回这么一大串异常信息给前端,所以需要对异常信息进行格式化,而且一般当发现第一个参数校验失败时就立即返回错误,不会进行接下来的校验

校验模式

从上面的错误信息可以得出,一次性返回了所有的校验错误信息,一般当第一个字段校验错误的时候就会直接返回错误了,不会进行接下来的校验,Hibernate Validator中正好也实现了这个功能,可参考官方文档

普通模式(默认)

会校验完所有的属性,如然后返回所有的错误信息

快速失败返回模式

只要有一个参数校验失败,则立即返回,官方提供了两种配置方式官方配置

  1. hibernate.validator.fail_fast:true 快速失败返回模式 false 普通模式
@configuration
public class ValidConfig{
	@Bean
	public Validator validator(){
	ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class)
                .configure()
                .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        return validator;
	}
}
  1. failFast:true 快速失败返回模式 false 普通模式
@configuration
public class ValidConfig{
	@Bean
	public Validator validator(){
	ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class)
                .configure()
                .failFast(true)
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        return validator;
	}
}

选择其中一种配置成功后,再次使用postman发送请求,发现错误信息只返回了一个

Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.fisher.valid.controller.ParamValidController.register(com.fisher.valid.vo.UserVO): [Field error in object 'userVO' on field 'password': rejected value []; codes [NotBlank.userVO.password,NotBlank.password,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userVO.password,password]; arguments []; default message [password]]; default message [密码不能为空]] ]
  1. 对异常信息进行格式化返回(上面报错异常为MethodArgumentNotValidException)
@RestControllerAdvice
public class ParamValidExceptionHandler {

    /**
     * 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。
     * 此方法无法格式化get请求里面 封装的VO参数
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String paramValidFailed(MethodArgumentNotValidException exception) {
        System.out.println("进入了1");
        BindingResult bindingResult = exception.getBindingResult();
        StringBuilder error = new StringBuilder();
        bindingResult.getAllErrors().forEach(e -> {
            error.append("[" + e.getDefaultMessage() + "],");
        });
        return error.substring(0, error.length() - 1).toString();
    }

此时发送请求返回的异常信息为

[密码不能为空]

hibernate参数校验的几种实现方式

get请求下校验没有封装的参数

  1. 需要重新配置校验是否快速返回
@Configuration
public class ValidConfig {

	/**
	 *
	 * @return 对单个参数进行验证时配置的快速失败返回
	 */
	@Bean
	public MethodValidationPostProcessor methodValidationPostProcessor() {
		MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();/**设置validator模式为快速失败返回*/
		postProcessor.setValidator(validator());
		return postProcessor;
	}

	/**
	 * 该配置设置验证失败快速返回 有一处校验失败就立马返回,只会显示一处错误
	 * @return
	 */
	@Bean
	public Validator validator() {
		ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
				.configure()
				.addProperty("hibernate.validator.fail_fast", "true")
				.buildValidatorFactory();
		Validator validator = validatorFactory.getValidator();
		return validator;
	}
}
  1. 在控制层类上添加@Validated注解
@RestController
@Validated
public class ParamValidController {
	 * @param userVO
	 * @return
	 */
	@GetMapping("/login")
	public String login(@RequestParam("username") @NotNull(message="用户名不合法")String username,@RequestParam("password") @NotNull(message="密码不合法")String password) {
		return "登录成功";
	}
}

访问上面接口时参数校验失败会报错

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ConstraintViolationException: login.username: 用户名不合法] with root cause
  1. 对异常信息就行格式化返回
@RestControllerAdvice
public class ParamValidExceptionHandler {

    /**
     * 单个参数校验时的异常拦截
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public String test(ConstraintViolationException exception) {
        System.out.println("进入了2");
        Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
        StringBuilder error = new StringBuilder();
        constraintViolations.forEach(e -> {
            error.append("[" + e.getMessage() + "],");
        });
        return error.substring(0, error.length() - 1).toString();
    }
}

get请求下校验封装后的对象

这是我遇到的坑,当我把get请求接口的参数用对象进行封装后再进行访问时,虽然能进行参数校验,但是不能格式化异常信息,最后发现之前的异常拦截器没有生效,访问这个接口会报错BindException,如下

@RestController
@Validated
public class ParamValidController {

	 * @param userVO
	 * @return
	 */
	@GetMapping("/login")
	public String login(@Validated UserVO userVO) {
		return "登录成功";
	}

报错信息

 Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'userVO' on field 'password': rejected value [null]; codes [NotBlank.userVO.password,NotBlank.password,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userVO.password,password]; arguments []; default message [password]]; default message [密码不能为空]]

这个时候需要对BindException进行拦截才能进行异常信息的格式化

@RestControllerAdvice
public class ParamValidExceptionHandler {
    /**
     * 处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(BindException.class)
    public String bindExceptionHandle(BindException exception) {
        System.out.println("进入了3");
        BindingResult bindingResult = exception.getBindingResult();
        StringBuilder error = new StringBuilder();
        bindingResult.getAllErrors().forEach(e -> {
            error.append("[" + e.getDefaultMessage() + "],");
        });
        return error.substring(0, error.length() - 1).toString();
    }
}

分组校验

  1. 编写实体类
@Data
public class UserVO {
	@NotBlank(message = "用户名不能为空", groups = {Register.class, Login.class})
	String username;
	@NotBlank(message = "密码不能为空", groups = {Register.class, Login.class})
	String password;
	@NotNull(message = "性别不能为空", groups = {Register.class})
	@Range(min = 0, max = 1, message = "性别不合法",groups = {Register.class})
	Integer sex;
	@Range(min = 18, message = "年龄不能小于18", groups = Register.class)
	@NotNull(message = "年龄不能为空", groups = {Register.class})
	Integer age;

	public interface Register {}

	public interface Login {}

}
  1. controller层在参数前面的@Validated添加对应的分组信息
@RestController
@Validated
public class ParamValidController {

	/**
	 * @param userVO
	 * @return
	 */
	@PostMapping("/register")
	public String register(@RequestBody @Validated(UserVO.Register.class) UserVO userVO) {
		return "注册成功";
	}

	 * @param userVO
	 * @return
	 */
	@GetMapping("/login")
	public String login(@Validated(UserVO.Login.class) UserVO userVO) {
		return "登录成功";
	}
}

常用的注解

@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

参考资料

https://www.cnblogs.com/mr-yang-localhost/p/7812038.html#_label3

上一篇:express-validator入门


下一篇:一文教你实现 SpringBoot 中的自定义 Validator 和错误信息国际化配置