• springBoot 中优雅的参数效验 hibernate-validator


    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 验证注解的元素值是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 {};
    }
    注意:message用于显示错误信息这个字段是必须的,groups和payload也是必须的
    @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;
    }


  • 相关阅读:
    2016012053小学四则运算练习软件项目报告
    读《构建之法》后的思考
    我与软件
    关于结对项目中网页字体的教程
    结对项目
    小学四则运算练习软件项目报告
    2016012028 赵莉 散列函数的应用及其安全性
    2016012028+四则运算练习软件项目报告(结对项目)
    《构建之法》阅读与思考——四、十七章
    2016012028+小学四则运算练习软件项目报告
  • 原文地址:https://www.cnblogs.com/dw3306/p/13028596.html
Copyright © 2020-2023  润新知