参数校验
在开发中经常需要做字段的参数校验,一般是长度,非空判断,以及一些手机号码和邮箱号的格式验证,而这些代码大多又是重复劳动且与业务逻辑没有太大关系
hibernate validator 提供了一套比较比较方便的参数校验实现方式
首先,先添加参数校验依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
注:spring-boot-starter-web
中已经添加了,引入了就不要再引入了
参数校验简单实现
先简单实现一个注册的参数校验的demo
-
controller层(参数
UserVO
前面需要添加注解@Validated
或者@Valid
)@RestController public class ParamValidController { /** * @param userVO * @return */ @PostMapping("/register") public String register(@RequestBody @Validated UserVO userVO) { return "注册成功"; } }
-
实体类
@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; }
-
使用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中正好也实现了这个功能,可参考官方文档
普通模式(默认)
会校验完所有的属性,如然后返回所有的错误信息
快速失败返回模式
只要有一个参数校验失败,则立即返回,官方提供了两种配置方式官方配置
- 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;
}
}
- 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 [密码不能为空]] ]
- 对异常信息进行格式化返回(上面报错异常为
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请求下校验没有封装的参数
- 需要重新配置校验是否快速返回
@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;
}
}
- 在控制层类上添加
@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
- 对异常信息就行格式化返回
@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();
}
}
分组校验
- 编写实体类
@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 {}
}
- 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 |
被注释的元素必须是电子邮箱地址 | |
@Length(min=,max=) | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range(min=,max=,message=) | 被注释的元素必须在合适的范围内 |
参考资料
https://www.cnblogs.com/mr-yang-localhost/p/7812038.html#_label3