springBoot 中优雅的参数效验 hibernate-validator
官网: http://hibernate.org/validator/documentation/
依赖:
spring-boot-starter-web
包里面有hibernate-validator
包,不需要引用hibernate validator依赖。在 pom.xml
中添加上 spring-boot-starter-web
的依赖即可
如果需要单独引入可以这样引入yml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
JSR 303 是Bean验证的规范 ,Hibernate Validator 是该规范的参考实现,它除了实现规范要求的注解外,还额外实现了一些注解。
@Null | 被注释的元素必须为 null |
@NotNull | 限制必须不为null |
@NotEmpty | 验证注解的元素值不为 null 且不为空(字符串长度不为0、集合大小不为0) |
@NotBlank | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
@Pattern(value) | 限制必须符合指定的正则表达式 |
@Size(max,min) | 限制字符长度必须在 min 到 max 之间(也可以用在集合上) |
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 | |
@Max(value) | 限制必须为一个不大于指定值的数字 |
@Min(value) | 限制必须为一个不小于指定值的数字 |
@DecimalMax(value) | 限制必须为一个不大于指定值的数字 |
@DecimalMin(value) | 限制必须为一个不小于指定值的数字 |
@AssertFalse | 限制必须为false (很少用) |
@AssertTrue | 限制必须为true (很少用) |
@Past | 限制必须是一个过去的日期 |
@Future | 限制必须是一个将来的日期 |
@Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction (很少用) |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@Size | 被注释的list、map在指定的大小范围 |
@Range | 被注释的元素必须在合适的范围内 |
@URL | 被注释的元素必须是有效URL |
@Negative | 负数 |
@NegativeOrZero | 0或者负数 |
@Positive | 整数 |
@PositiveOrZero | 0或者整数 |
Valid | 递归的对关联的对象进行校验(对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部的验证) |
eg:
public class BaseEntity {
@Min(value = 1,message = "必须大于0")
private Integer pageSize=1;
@Range(min = 1,max = 1000,message = "一次性获取最大列表数不能超过1000")
private Integer pageNum=10;
}
参数校验模式:
1:hibernate的校验模式 Hibernate Validator有以下两种验证模式:
1、普通模式(默认是这个模式)
2、快速失败返回模式
配置:
@Configuration
public class ValidatorConfiguration {
@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:post方式参数效验:
@Getter
@Setter
@NoArgsConstructor
public class DemoModel {
@NotBlank(message="用户名不能为空")
private String userName;
@NotBlank(message="年龄不能为空")
@Pattern(regexp="^[0-9]{1,2}$",message="年龄不正确")
private String age;
@AssertFalse(message = "必须为false")
private Boolean isFalse;
/**
* 如果是空,则不校验,如果不为空,则校验
*/
@Pattern(regexp="^[0-9]{4}-[0-9]{2}-[0-9]{2}$",message="出生日期格式不正确")
private String birthday;
}
POST接口验证,BindingResult是验证不通过的结果集合:
@RequestMapping("/demo2")
public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}
在@RequestBody DemoModel demo之间加注解 @Valid,然后后面加BindindResult即可;多个参数的,可以加多个@Valid和BindingResult,如:public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)
2、GET方式参数校验(@RequestParam参数校验)
方法所在的Controller上加注解 @Validated
@RequestMapping("/validation")
@RestController
@Validated
public class ValidationController {
/**如果只有少数对象,直接把参数写到Controller层,然后在Controller层进行验证就可以了。*/
@RequestMapping(value = "/demo3", method = RequestMethod.GET)
public void demo3(@Range(min = 1, max = 9, message = "年级只能从1-9")
@RequestParam(name = "grade", required = true)
int grade,
@Min(value = 1, message = "班级最小只能1")
@Max(value = 99, message = "班级最大只能99")
@RequestParam(name = "classroom", required = true)
int classroom) {
System.out.println(grade + "," + classroom);
}
}
下面这一步在springboot其实也不用做,ValidationAutoConfiguration这个配置类自动帮我们做了,如果不是springboot此时需要使用MethodValidationPostProcessor 的Bean:
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
/**默认是普通模式,会返回所有的验证不通过信息集合*/
return new MethodValidationPostProcessor();
}
或者指定快速失败:
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
/**设置validator模式为快速失败返回*/
postProcessor.setValidator(validator());
return postProcessor;
}
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
验证不通过时,抛出了ConstraintViolationException异常,使用全局统一捕获异常处理:
/**
* @Author dw
* @ClassName GlobleExceptionHandler
* @Description
* @Date 2020/6/3 15:49
* @Version 1.0
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 请求方法中校验抛出的异常
* @param ex
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public String resolveConstraintViolationException(ConstraintViolationException ex){
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
if(!CollectionUtils.isEmpty(constraintViolations)){
StringBuilder msgBuilder = new StringBuilder();
for(ConstraintViolation constraintViolation: constraintViolations){
msgBuilder.append(constraintViolation.getMessage()).append(",");
}
String errorMessage = msgBuilder.toString();
return errorMessage;
}
return ex.getMessage();
}
/**
* get请求参数校验抛出的异常
* @param bindException
* @return
*/
@ExceptionHandler(BindException.class)
public String bindExceptionHandler(BindException bindException){
//获取异常中随机一个异常信息
// String defaultMessage = e.getBindingResult().getFieldError().getDefaultMessage();
StringBuilder msgBuilder = new StringBuilder();
if (bindException.hasErrors()){
for (FieldError fieldError : bindException.getFieldErrors()) {
msgBuilder.append(fieldError.getDefaultMessage());
}
}
return msgBuilder.toString();
}
/**
* post请求参数校验抛出的异常
* @param ex
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public String resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex){
List<ObjectError> objectErrors = ex.getBindingResult().getAllErrors();
if(!CollectionUtils.isEmpty(objectErrors)) {
StringBuilder msgBuilder = new StringBuilder();
for (ObjectError objectError : objectErrors) {
msgBuilder.append(objectError.getDefaultMessage()).append(",");
}
String errorMessage = msgBuilder.toString();
return errorMessage;
}
return ex.getMessage();
}
}
将所有的校验异常都在异常统一处理后,controller参数中就可以不写BindingResult,也不需要每个方法都校验是否有异常了
使用异常统一处理后的controller
@RestController
public class UserBetterController {
@PostMapping("/login")
public JsonResult login(@Validated @RequestBody User user){
return JsonResult.success("登陆成功");
}
@GetMapping("/getLogin")
public JsonResult getLogin(@Validated User user){
return JsonResult.success("登陆成功");
}
@GetMapping("/getUser")
public JsonResult getUser(@NotNull(message = "用户名不能为空") String username){
User user = new User();
user.setUsername(username);
user.setPassword("123456");
return JsonResult.success(user);
}
}
分组校验:
比如:有这样一种场景,新增用户信息的时候,不需要验证userId(因为系统生成);修改的时候需要验证userId,这时候可用用户到validator的分组验证功能。
定义两个接口groupA、groupB
public interface GroupA {
}
public interface GroupB {
}
验证model:Person
@Data
public class Person {
@NotBlank
@Range(min = 1,max = Integer.MAX_VALUE,message = "必须大于0",groups = {GroupA.class})
/**用户id*/
private Integer userId;
@NotBlank
@Length(min = 4,max = 20,message = "必须在[4,20]",groups = {GroupB.class})
/**用户名*/
private String userName;
@NotBlank
@Range(min = 0,max = 100,message = "年龄必须在[0,100]",groups={Default.class})
/**年龄*/
private Integer age;
@Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {GroupB.class})
/**性别 0:未知;1:男;2:女*/
private Integer sex;
}
如上Person所示,3个分组分别验证字段如下:
- GroupA验证字段userId;
- GroupB验证字段userName、sex;
- Default验证字段age(Default是Validator自带的默认分组)
controller验证:
@RequestMapping("/demo6")
public void demo6(@Validated({GroupA.class, GroupB.class}) Person p, BindingResult result){
if(result.hasErrors()){
List<ObjectError> allErrors = result.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println(error);
}
}
}
自定义校验注解
1.创建约束注解类
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented @Constraint(validatedBy = { MyValidator.class}) public @interface MyVolidate { String message() default "默认的错误信息"; String name(); Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default {}; }
@Constraint(validatedBy = { MyValidator.class})用来指定处理这个注解逻辑的类
2.创建验证器类
public class MyValidator implements ConstraintValidator<MyVolidate, User> { private String name; /** * 用于初始化注解上的值到这个validator * @param constraintAnnotation */ @Override public void initialize(MyVolidate constraintAnnotation) { name =constraintAnnotation.name(); } /** * 具体的校验逻辑 * @param value * @param context * @return */ @Override public boolean isValid(User value, ConstraintValidatorContext context) { return name ==null || name.equals(value.getName()); } }
3. 测试
@PostMapping("/validateTest")
@ResponseBody
public User validateTest(@Valid @MyVolidate(name = "成都",message = "如果效验不通过,显示我") @RequestBody User user){
return user;
}