精华内容
下载资源
问答
  • 在开发中经常需要写一些字段校验代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要看哪些参数...

    一、参数校验

     在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦:

    • 验证代码繁琐,重复劳动
    • 方法内代码显得冗长
    • 每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码

    hibernate validator(官方文档)提供了一套比较完善、便捷的验证实现方式。

    spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。

    二、hibernate validator校验demo

     先来看一个简单的demo,添加了Validator的注解:

    import org.hibernate.validator.constraints.NotBlank;
    import javax.validation.constraints.AssertFalse;
    import javax.validation.constraints.Pattern;
    @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());
      }
     }
     }

    POST请求传入的参数:{"userName":"dd","age":120,"isFalse":true,"birthday":"21010-21-12"}

    输出结果:

    出生日期格式不正确
    必须为false
    年龄不正确

     参数验证非常方便,字段上注解+验证不通过提示信息即可代替手写一大堆的非空和字段限制验证代码。下面深入了解下参数校验的玩法。

    三、hibernate的校验模式

    细心的读者肯定发现了:上面例子中一次性返回了所有验证不通过的集合,通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了。Hibernate Validator有以下两种验证模式:

    1、普通模式(默认是这个模式)

      普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

    2、快速失败返回模式

      快速失败返回模式(只要有一个验证失败,则返回)

    两种验证模式配置方式:(参考官方文档

    failFast:true  快速失败返回模式    false 普通模式

    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
     .configure()
     .failFast( true )
     .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();

    和 (hibernate.validator.fail_fast:true  快速失败返回模式    false 普通模式)

    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
     .configure()
     .addProperty( "hibernate.validator.fail_fast", "true" )
     .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();

    四、hibernate的两种校验

    配置hibernate Validator为快速失败返回模式:

    @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、请求参数校验

    如demo里示例的,验证请求参数时,在@RequestBody DemoModel demo之间加注解 @Valid,然后后面加BindindResult即可;多个参数的,可以加多个@Valid和BindingResult,如:

    public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)
    public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)
     @RequestMapping("/demo2")
     public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){
     if(result.hasErrors()){
      for (ObjectError error : result.getAllErrors()) {
      System.out.println(error.getDefaultMessage());
      }
     }
     }

    2、GET参数校验(@RequestParam参数校验)

    使用校验bean的方式,没有办法校验RequestParam的内容,一般在处理Get请求(或参数比较少)的时候,会使用下面这样的代码:

     @RequestMapping(value = "/demo3", method = RequestMethod.GET)
     public void demo3(@RequestParam(name = "grade", required = true) int grade,@RequestParam(name = "classroom", required = true) int classroom) {
     System.out.println(grade + "," + classroom);
     }

    使用@Valid注解,对RequestParam对应的参数进行注解,是无效的,需要使用@Validated注解来使得验证生效。如下所示:

    a.此时需要使用MethodValidationPostProcessor 的Bean:

     @Bean
     public MethodValidationPostProcessor methodValidationPostProcessor() {
         /**默认是普通模式,会返回所有的验证不通过信息集合*/
     return new MethodValidationPostProcessor();
     }

    或 可对MethodValidationPostProcessor 进行设置Validator(因为此时不是用的Validator进行验证,Validator的配置不起作用)

    @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;
     }

    b.方法所在的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);
     }
    }

    c.返回验证信息提示

    可以看到:验证不通过时,抛出了ConstraintViolationException异常,使用同一捕获异常处理:

    @ControllerAdvice
    @Component
    public class GlobalExceptionHandler {
     @ExceptionHandler
     @ResponseBody
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     public String handle(ValidationException exception) {
     if(exception instanceof ConstraintViolationException){
      ConstraintViolationException exs = (ConstraintViolationException) exception;
      Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
      for (ConstraintViolation<?> item : violations) {
              /**打印验证不通过的信息*/
      System.out.println(item.getMessage());
      }
     }
     return "bad request, " ;
     }
    }

    d.验证

    浏览器服务请求地址:http://localhost:8080/validation/demo3?grade=18&classroom=888

    没有配置快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9
    班级最大只能99

    配置了快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9

    浏览器服务请求地址:http://localhost:8080/validation/demo3?grade=0&classroom=0

    没有配置快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9
    班级最小只能1

    配置了快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9

    3、model校验

    待校验的model:

    @Data
    public class Demo2 {
     @Length(min = 5, max = 17, message = "length长度在[5,17]之间")
     private String length;
     /**@Size不能验证Integer,适用于String, Collection, Map and arrays*/
     @Size(min = 1, max = 3, message = "size在[1,3]之间")
     private String age;
     @Range(min = 150, max = 250, message = "range在[150,250]之间")
     private int high;
     @Size(min = 3,max = 5,message = "list的Size在[3,5]")
     private List<String> list;
    }

    验证model,以下全部验证通过:

     @Autowired
     private Validator validator;
     @RequestMapping("/demo3")
     public void demo3(){
     Demo2 demo2 = new Demo2();
     demo2.setAge("111");
     demo2.setHigh(150);
     demo2.setLength("ABCDE");
     demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});
     Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
     for (ConstraintViolation<Demo2> model : violationSet) {
      System.out.println(model.getMessage());
     }
     }

    4、对象级联校验

    对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部的验证:(验证Demo2示例时,可以验证Demo2的字段)

    @Data
    public class Demo2 {
     @Size(min = 3,max = 5,message = "list的Size在[3,5]")
     private List<String> list;
     @NotNull
     @Valid
     private Demo3 demo3;
    }
    @Data
    public class Demo3 {
     @Length(min = 5, max = 17, message = "length长度在[5,17]之间")
     private String extField;
    }

    级联校验:

     /**前面配置了快速失败返回的Bean*/
     @Autowired
     private Validator validator;
     @RequestMapping("/demo3")
     public void demo3(){
     Demo2 demo2 = new Demo2();
     demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});
     Demo3 demo3 = new Demo3();
     demo3.setExtField("22");
     demo2.setDemo3(demo3);
     Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
     for (ConstraintViolation<Demo2> model : violationSet) {
      System.out.println(model.getMessage());
     }
     }

    可以校验Demo3的extField字段。

    5、分组校验

    结论:分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。

    有这样一种场景,新增用户信息的时候,不需要验证userId(因为系统生成);修改的时候需要验证userId,这时候可用用户到validator的分组验证功能。

    设置validator为普通验证模式("hibernate.validator.fail_fast", "false"),用到的验证GroupA、GroupB和model:

    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自带的默认分组)

    a、分组

    只验证GroupA、GroupB标记的分组:

    @RequestMapping("/demo5")
    public void demo5(){
     Person p = new Person();
     /**GroupA验证不通过*/
     p.setUserId(-12);
     /**GroupA验证通过*/
     //p.setUserId(12);
     p.setUserName("a");
     p.setAge(110);
     p.setSex(5);
     Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupA.class, GroupB.class);
     for (ConstraintViolation<Person> item : validate) {
     System.out.println(item);
     }
    }

     @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);
      }
     }
     }

    GroupA、GroupB、Default都验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='必须大于0', propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须大于0'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}

    GroupA验证通过、GroupB、Default验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}

    b、组序列

    除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证:

    指定组的序列(GroupA》GroupB》Default):

    @GroupSequence({GroupA.class, GroupB.class, Default.class})
    public interface GroupOrder {
    }

    测试demo:

     @RequestMapping("/demo7")
     public void demo7(){
     Person p = new Person();
     /**GroupA验证不通过*/
     //p.setUserId(-12);
     /**GroupA验证通过*/
     p.setUserId(12);
     p.setUserName("a");
     p.setAge(110);
     p.setSex(5);
     Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupOrder.class);
     for (ConstraintViolation<Person> item : validate) {
      System.out.println(item);
     }
     }

     @RequestMapping("/demo8")
     public void demo8(@Validated({GroupOrder.class}) Person p, BindingResult result){
     if(result.hasErrors()){
      List<ObjectError> allErrors = result.getAllErrors();
      for (ObjectError error : allErrors) {
      System.out.println(error);
      }
     }
     }

    GroupA、GroupB、Default都验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须大于0', propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须大于0'}

    GroupA验证通过、GroupB、Default验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}

    结论:分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。

    五、自定义验证器

    一般情况,自定义验证可以解决很多问题。但也有无法满足情况的时候,此时,我们可以实现validator的接口,自定义自己需要的验证器。

    如下所示,实现了一个自定义的大小写验证器:

    public enum CaseMode {
     UPPER,
     LOWER;
    }
    @Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = CheckCaseValidator.class)
    @Documented
    public @interface CheckCase {
     String message() default "";
     Class<?>[] groups() default {};
     Class<? extends Payload>[] payload() default {};
     CaseMode value();
    }
    public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
     private CaseMode caseMode;
     public void initialize(CheckCase checkCase) {
     this.caseMode = checkCase.value();
     }
     public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
     if (s == null) {
      return true;
     }
     if (caseMode == CaseMode.UPPER) {
      return s.equals(s.toUpperCase());
     } else {
      return s.equals(s.toLowerCase());
     }
     }
    }

    要验证的Model:

     public class Demo{
     @CheckCase(value = CaseMode.LOWER,message = "userName必须是小写")
     private String userName;
     public String getUserName() {
      return userName;
     }
     public void setUserName(String userName) {
      this.userName = userName;
     }
     }

    validator配置:

    @Bean
     public Validator validator(){
     ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
      .configure()
      .addProperty( "hibernate.validator.fail_fast", "true" )
      .buildValidatorFactory();
     Validator validator = validatorFactory.getValidator();
     return validator;
     }

    验证测试:

     @RequestMapping("/demo4")
     public void demo4(){
     Demo demo = new Demo();
     demo.setUserName("userName");
     Set<ConstraintViolation<Demo>> validate = validator.validate(demo);
     for (ConstraintViolation<Demo> dem : validate) {
      System.out.println(dem.getMessage());
     }
     }

    输出结果:

    userName必须是小写

    六、常见的注解

     Bean Validation 中内置的 constraint 
    @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=) 被注释的元素必须符合指定的正则表达式 
    Hibernate Validator 附加的 constraint 
    @NotBlank(message =) 验证字符串非null,且长度必须大于0 
    @Email 被注释的元素必须是电子邮箱地址 
    @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内 
    @NotEmpty 被注释的字符串的必须非空 
    @Range(min=,max=,message=) 被注释的元素必须在合适的范围内
    //大于0.01,不包含0.01
    @NotNull
    @DecimalMin(value = "0.01", inclusive = false)
    private Integer greaterThan;
    //大于等于0.01
    @NotNull
    @DecimalMin(value = "0.01", inclusive = true)
    private BigDecimal greatOrEqualThan;
    @Length(min = 1, max = 20, message = "message不能为空")
    //不能将Length错用成Range
    //@Range(min = 1, max = 20, message = "message不能为空")
    private String message;



    不使用注解:(使用Validator进行手工的处理)
    ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
    .configure()
    .failFast(true)
    .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();
    return validator.validate(t).stream().map(p -> p.getMessage()).collect(Collectors.toList());

    以下代码可封装进BaseController,使用自定义异常的方式抛出,然后在使用统一异常处理机制,进行异常拦截处理(为什么要这么做?这样做可能会是代码看上去更优雅)
    List<String> errors = ValidateUtil.validate(obj);
    if (null != errors && errors.size() > 0) {
    throw new ApiException(BizErrorCodeEnum.PARAM_ILLEGAL.getCode(), errors.get(0));
    }

    七、参考资料

    参考资料:

    http://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html_single/#validator-gettingstarted

    转载于:https://www.cnblogs.com/swugogo/p/9885038.html

    展开全文
  • EasyExcel注解方式校验数据行

    万次阅读 2019-09-09 23:10:38
    easyexcel-wraper有哪些功能? 在easyexcel的基础上进行封装,方便读取excel内容,避免在主业务代码中嵌入重复繁琐的样本代码 支持Hibernate-validator验证框架,可以使用诸如@NotBlank,@NotDuiplicate的注解 如何...

    EasyExcel注解方式校验数据行

    一个方便读取excel内容,且可以使用注解进行内容验证的包装工具

    easyexcel-wraper有哪些功能?

    • 在easyexcel的基础上进行封装,方便读取excel内容,避免在主业务代码中嵌入重复繁琐的样本代码
    • 支持Hibernate-validator验证框架,可以使用诸如@NotBlank,@NotDuiplicate的注解

    如何使用

    • 1、新建一个JavaBean用于接收excel内容,并继承ExcelRow基础类,如:新建MyRow.java
    import com.alibaba.excel.annotation.ExcelProperty;
    import com.wuyue.excel.ExcelRow;
    import com.wuyue.excel.validate.NotDuplicate;
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    import org.hibernate.validator.constraints.Email;
    import org.hibernate.validator.constraints.NotBlank;
    
    @Getter
    @Setter
    @ToString
    public class MyRow extends ExcelRow {
    
        @ExcelProperty(index = 0)
        @NotDuplicate
        @NotBlank(message = "名称不能为空")
        private String name;
    
        @ExcelProperty(index = 1)
        @Email
        private String email;
    
    }
    
    
    • 2、调用ExcelReader.read方法,获取excel内容
        File file = new File("D:\\1.xlsx");
        FileInputStream fileInputStream = new FileInputStream(file);
        List<MyRow> rows = ExcelReader.builder()
                .inputStream(fileInputStream)
                .sheetNo(1)
                .headLineMun(1)
                .build()
                .read(MyRow.class);
    
    • 3、主业务功能代码使用“行”内容的校验结果
        System.out.println(rows);
        rows.forEach(row -> {
            // 行号,如果要提示实际excel行号,应该要加上headLineMun的值
            System.out.print("Row number:" + row.getRowNum());
            // 校验结果代码(0为正常)
            System.out.print(", validate code:" + row.getValidateCode());
            // 校验结果内容
            System.out.println(", message:" + row.getValidateMessage());
        });
    

    当D:\1.xlsx的内容为如下时

    [外链图片转存失败(img-k6ATmMEU-1568041808052)(https://github.com/Felix0525/assets/blob/master/20190501-0001.png?raw=true)]

    输出结果如下:

        [MyRow(name=felix, email=5401142), MyRow(name=wuyue, email=540114289@qq.com), MyRow(name=felix, email=null), MyRow(name=wuyue, email=null)]
        Row number:1, validate code:2, message:不是一个合法的电子邮件地址
        Row number:2, validate code:0, message:null
        Row number:3, validate code:1, message:Duplicate field
        Row number:4, validate code:1, message:Duplicate field
    

    完整源码

    展开全文
  • 在开发中经常需要写一些字段校验代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要看哪些参数验证...

    一、参数校验

    在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦:

    • 验证代码繁琐,重复劳动
    • 方法内代码显得冗长
    • 每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码
      hibernate validator (官方文档)提供了一套比较完善、便捷的验证实现方式。

    spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。

    二、hibernate validator校验demo

    先来看一个简单的demo,添加了Validator的注解:

    import org.hibernate.validator.constraints.NotBlank;
    
    import javax.validation.constraints.AssertFalse;
    import javax.validation.constraints.Pattern;
    复制代码
    @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());
                }
            }
        }
    

    POST请求传入的参数:{“userName”:“dd”,“age”:120,“isFalse”:true,“birthday”:“21010-21-12”}

    输出结果:

    出生日期格式不正确
    必须为false
    年龄不正确
    

    参数验证非常方便,字段上注解+验证不通过提示信息即可代替手写一大堆的非空和字段限制验证代码。下面深入了解下参数校验的玩法。

    三、hibernate的校验模式

    细心的读者肯定发现了:上面例子中一次性返回了所有验证不通过的集合,通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了。Hibernate Validator有以下两种验证模式:

    返回目录

    1、普通模式(默认是这个模式)

    普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

    2、快速失败返回模式

    快速失败返回模式(只要有一个验证失败,则返回)

    两种验证模式配置方式:(参考官方文档

    failFast:true 快速失败返回模式 false 普通模式

    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
            .configure()
            .failFast( true )
            .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();
    

    和 (hibernate.validator.fail_fast:true 快速失败返回模式 false 普通模式)

    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
            .configure()
            .addProperty( "hibernate.validator.fail_fast", "true" )
            .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();
    

    四、hibernate的两种校验

    配置hibernate Validator为快速失败返回模式:

    @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、请求参数校验

    如demo里示例的,验证请求参数时,在@RequestBody DemoModel demo之间加注解 @Valid,然后后面加BindindResult即可;多个参数的,可以加多个@Valid和BindingResult,如:

    public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)

    public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)

        @RequestMapping("/demo2")
        public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){
            if(result.hasErrors()){
                for (ObjectError error : result.getAllErrors()) {
                    System.out.println(error.getDefaultMessage());
                }
            }
        }
    

    2、GET参数校验(@RequestParam参数校验)

    使用校验bean的方式,没有办法校验RequestParam的内容,一般在处理Get请求(或参数比较少)的时候,会使用下面这样的代码:

        @RequestMapping(value = "/demo3", method = RequestMethod.GET)
        public void demo3(@RequestParam(name = "grade", required = true) int grade,@RequestParam(name = "classroom", required = true) int classroom) {
            System.out.println(grade + "," + classroom);
        }
    

    使用@Valid注解,对RequestParam对应的参数进行注解,是无效的,需要使用@Validated注解来使得验证生效。如下所示:

    a.此时需要使用MethodValidationPostProcessor 的Bean:

        @Bean
        public MethodValidationPostProcessor methodValidationPostProcessor() {
          /**默认是普通模式,会返回所有的验证不通过信息集合*/
            return new MethodValidationPostProcessor();
        }
    

    或 可对MethodValidationPostProcessor 进行设置Validator(因为此时不是用的Validator进行验证,Validator的配置不起作用)

        @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;
        }
    

    b.方法所在的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);
        }
    }
    

    c.返回验证信息提示

    可以看到:验证不通过时,抛出了ConstraintViolationException异常,使用同一捕获异常处理:

    @ControllerAdvice
    @Component
    public class GlobalExceptionHandler {
    
        @ExceptionHandler
        @ResponseBody
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public String handle(ValidationException exception) {
            if(exception instanceof ConstraintViolationException){
                ConstraintViolationException exs = (ConstraintViolationException) exception;
    
                Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
                for (ConstraintViolation<?> item : violations) {
              /**打印验证不通过的信息*/
                    System.out.println(item.getMessage());
                }
            }
            return "bad request, " ;
        }
    }
    

    d.验证

    浏览器服务请求地址:http://localhost:8080/validation/demo3?grade=18&classroom=888

    没有配置快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9
    班级最大只能99
    

    配置了快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9
    

    浏览器服务请求地址:http://localhost:8080/validation/demo3?grade=0&classroom=0

    没有配置快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9
    班级最小只能1
    

    配置了快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9
    

    3、model校验

    待校验的model:

    @Data
    public class Demo2 {
        @Length(min = 5, max = 17, message = "length长度在[5,17]之间")
        private String length;
    
        /**@Size不能验证Integer,适用于String, Collection, Map and arrays*/
        @Size(min = 1, max = 3, message = "size在[1,3]之间")
        private String age;
    
        @Range(min = 150, max = 250, message = "range在[150,250]之间")
        private int high;
    
        @Size(min = 3,max = 5,message = "list的Size在[3,5]")
        private List<String> list;
    }
    

    验证model,以下全部验证通过:

        @Autowired
        private Validator validator;
    
        @RequestMapping("/demo3")
        public void demo3(){
            Demo2 demo2 = new Demo2();
            demo2.setAge("111");
            demo2.setHigh(150);
            demo2.setLength("ABCDE");
            demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});
            Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
            for (ConstraintViolation<Demo2> model : violationSet) {
                System.out.println(model.getMessage());
            }
        }
    

    4、对象级联校验

    对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部的验证:(验证Demo2示例时,可以验证Demo2的字段)

    @Data
    public class Demo2 {
        @Size(min = 3,max = 5,message = "list的Size在[3,5]")
        private List<String> list;
    
        @NotNull
        @Valid
        private Demo3 demo3;
    }
    
    @Data
    public class Demo3 {
        @Length(min = 5, max = 17, message = "length长度在[5,17]之间")
        private String extField;
    }
    

    级联校验:

        /**前面配置了快速失败返回的Bean*/
        @Autowired
        private Validator validator;
    
        @RequestMapping("/demo3")
        public void demo3(){
            Demo2 demo2 = new Demo2();
            demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});
    
            Demo3 demo3 = new Demo3();
            demo3.setExtField("22");
            demo2.setDemo3(demo3);
            Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
            for (ConstraintViolation<Demo2> model : violationSet) {
                System.out.println(model.getMessage());
            }
        }
    

    可以校验Demo3的extField字段。

    5、分组校验

    结论:分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。

    有这样一种场景,新增用户信息的时候,不需要验证userId(因为系统生成);修改的时候需要验证userId,这时候可用用户到validator的分组验证功能。

    设置validator为普通验证模式(“hibernate.validator.fail_fast”, “false”),用到的验证GroupA、GroupB和model:

    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自带的默认分组)

    a、分组

    只验证GroupA、GroupB标记的分组:

    @RequestMapping("/demo5")
    public void demo5(){
        Person p = new Person();
        /**GroupA验证不通过*/
        p.setUserId(-12);
        /**GroupA验证通过*/
        //p.setUserId(12);
        p.setUserName("a");
        p.setAge(110);
        p.setSex(5);
        Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupA.class, GroupB.class);
        for (ConstraintViolation<Person> item : validate) {
            System.out.println(item);
        }
    }
    

        @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);
                }
            }
        }
    

    GroupA、GroupB、Default都验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='必须大于0', propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须大于0'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}
    

    GroupA验证通过、GroupB、Default验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}
    

    b、组序列

    除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证:

    指定组的序列(GroupA》GroupB》Default):

    @GroupSequence({GroupA.class, GroupB.class, Default.class})
    public interface GroupOrder {
    }
    

    测试demo:

        @RequestMapping("/demo7")
        public void demo7(){
            Person p = new Person();
            /**GroupA验证不通过*/
            //p.setUserId(-12);
            /**GroupA验证通过*/
            p.setUserId(12);
            p.setUserName("a");
            p.setAge(110);
            p.setSex(5);
            Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupOrder.class);
            for (ConstraintViolation<Person> item : validate) {
                System.out.println(item);
            }
        }
    

        @RequestMapping("/demo8")
        public void demo8(@Validated({GroupOrder.class}) Person p, BindingResult result){
            if(result.hasErrors()){
                List<ObjectError> allErrors = result.getAllErrors();
                for (ObjectError error : allErrors) {
                    System.out.println(error);
                }
            }
        }
    

    GroupA、GroupB、Default都验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须大于0', propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须大于0'}
    

    GroupA验证通过、GroupB、Default验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}
    

    结论:分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。

    五、自定义验证器

    一般情况,自定义验证可以解决很多问题。但也有无法满足情况的时候,此时,我们可以实现validator的接口,自定义自己需要的验证器。

    如下所示,实现了一个自定义的大小写验证器:

    public enum CaseMode {
        UPPER,
        LOWER;
    }
    
    
    @Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = CheckCaseValidator.class)
    @Documented
    public @interface CheckCase {
        String message() default "";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        CaseMode value();
    }
    
    
    public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
        private CaseMode caseMode;
        public void initialize(CheckCase checkCase) {
            this.caseMode = checkCase.value();
        }
    
        public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
            if (s == null) {
                return true;
            }
    
            if (caseMode == CaseMode.UPPER) {
                return s.equals(s.toUpperCase());
            } else {
                return s.equals(s.toLowerCase());
            }
        }
    }
    

    要验证的Model:

        public class Demo{
            @CheckCase(value = CaseMode.LOWER,message = "userName必须是小写")
            private String userName;
    
            public String getUserName() {
                return userName;
            }
    
            public void setUserName(String userName) {
                this.userName = userName;
            }
        }
    

    validator配置:

        @Bean
        public Validator validator(){
            ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                    .configure()
                    .addProperty( "hibernate.validator.fail_fast", "true" )
                    .buildValidatorFactory();
            Validator validator = validatorFactory.getValidator();
    
            return validator;
        }
    

    验证测试:

        @RequestMapping("/demo4")
        public void demo4(){
            Demo demo = new Demo();
            demo.setUserName("userName");
            Set<ConstraintViolation<Demo>> validate = validator.validate(demo);
            for (ConstraintViolation<Demo> dem : validate) {
                System.out.println(dem.getMessage());
            }
        }
    

    输出结果:

    userName必须是小写
    

    六、常见的注解

      Bean Validation 中内置的 constraint     
    @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=)  被注释的元素必须符合指定的正则表达式     
    Hibernate Validator 附加的 constraint     
    @NotBlank(message =)   验证字符串非null,且长度必须大于0     
    @Email  被注释的元素必须是电子邮箱地址     
    @Length(min=,max=)  被注释的字符串的大小必须在指定的范围内     
    @NotEmpty   被注释的字符串的必须非空     
    @Range(min=,max=,message=)  被注释的元素必须在合适的范围内
    
    //大于0.01,不包含0.01
    @NotNull
    @DecimalMin(value = "0.01", inclusive = false)
    private Integer greaterThan;
    
    //大于等于0.01
    @NotNull
    @DecimalMin(value = "0.01", inclusive = true)
    private BigDecimal greatOrEqualThan;
    
    @Length(min = 1, max = 20, message = "message不能为空")
    //不能将Length错用成Range
    //@Range(min = 1, max = 20, message = "message不能为空")
    private String message;
    
    展开全文
  • 在开发中经常需要写一些字段校验代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉两个麻烦:验证代码繁琐,重复劳动方法内代码显得冗长每次要看哪些参数验证是否...

    一、参数校验

     在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦:

    • 验证代码繁琐,重复劳动
    • 方法内代码显得冗长
    • 每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码

    hibernate validator(官方文档)提供了一套比较完善、便捷的验证实现方式。

    spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。

    二、hibernate validator校验demo

     先来看一个简单的demo,添加了Validator的注解:

    import org.hibernate.validator.constraints.NotBlank;

    import javax.validation.constraints.AssertFalse;
    import javax.validation.constraints.Pattern;
    复制代码
    @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());
                }
            }
        }
    复制代码

    POST请求传入的参数:{"userName":"dd","age":120,"isFalse":true,"birthday":"21010-21-12"}

    输出结果:

    出生日期格式不正确
    必须为false
    年龄不正确

     参数验证非常方便,字段上注解+验证不通过提示信息即可代替手写一大堆的非空和字段限制验证代码。下面深入了解下参数校验的玩法。

    本文地址:http://www.cnblogs.com/mr-yang-localhost/p/7812038.html

    三、hibernate的校验模式

    细心的读者肯定发现了:上面例子中一次性返回了所有验证不通过的集合,通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了。Hibernate Validator有以下两种验证模式:

    1、普通模式(默认是这个模式)

      普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

    2、快速失败返回模式

      快速失败返回模式(只要有一个验证失败,则返回)

    两种验证模式配置方式:(参考官方文档

    failFast:true  快速失败返回模式    false 普通模式 

    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
    .configure()
    .failFast( true )
    .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();

    和 (hibernate.validator.fail_fast:true  快速失败返回模式    false 普通模式

    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
    .configure()
    .addProperty( "hibernate.validator.fail_fast", "true" )
    .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();

    四、hibernate的两种校验

    配置hibernate Validator为快速失败返回模式:

    复制代码
    @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、请求参数校验

    如demo里示例的,验证请求参数时,在@RequestBody DemoModel demo之间加注解 @Valid,然后后面加BindindResult即可;多个参数的,可以加多个@Valid和BindingResult,如:

    public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)

    public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)

    复制代码
        @RequestMapping("/demo2")
        public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){
            if(result.hasErrors()){
                for (ObjectError error : result.getAllErrors()) {
                    System.out.println(error.getDefaultMessage());
                }
            }
        }
    复制代码

    2、GET参数校验(@RequestParam参数校验)

    使用校验bean的方式,没有办法校验RequestParam的内容,一般在处理Get请求(或参数比较少)的时候,会使用下面这样的代码:

        @RequestMapping(value = "/demo3", method = RequestMethod.GET)
        public void demo3(@RequestParam(name = "grade", required = true) int grade,@RequestParam(name = "classroom", required = true) int classroom) {
            System.out.println(grade + "," + classroom);
        }

    使用@Valid注解,对RequestParam对应的参数进行注解,是无效的,需要使用@Validated注解来使得验证生效。如下所示:

    a.此时需要使用MethodValidationPostProcessor 的Bean:

        @Bean
        public MethodValidationPostProcessor methodValidationPostProcessor() {
         /**默认是普通模式,会返回所有的验证不通过信息集合*/
    return new MethodValidationPostProcessor(); }

    或 可对MethodValidationPostProcessor 进行设置Validator(因为此时不是用的Validator进行验证,Validator的配置不起作用)

    复制代码
        @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; }
    复制代码

    b.方法所在的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);
        }
    }
    复制代码

    c.返回验证信息提示

    可以看到:验证不通过时,抛出了ConstraintViolationException异常,使用同一捕获异常处理:

    复制代码
    @ControllerAdvice
    @Component
    public class GlobalExceptionHandler {
    
        @ExceptionHandler
        @ResponseBody
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public String handle(ValidationException exception) {
            if(exception instanceof ConstraintViolationException){
                ConstraintViolationException exs = (ConstraintViolationException) exception;
    
                Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
                for (ConstraintViolation<?> item : violations) {
              /**打印验证不通过的信息*/ System.out.println(item.getMessage()); } }
    return "bad request, " ; } }
    复制代码

    d.验证

    浏览器服务请求地址:http://localhost:8080/validation/demo3?grade=18&classroom=888

    没有配置快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9
    班级最大只能99

    配置了快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9

    浏览器服务请求地址:http://localhost:8080/validation/demo3?grade=0&classroom=0

    没有配置快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9
    班级最小只能1

    配置了快速失败返回的MethodValidationPostProcessor 时输出信息如下:

    年级只能从1-9

    3、model校验

    待校验的model:

    复制代码
    @Data
    public class Demo2 {
        @Length(min = 5, max = 17, message = "length长度在[5,17]之间")
        private String length;
    
        /**@Size不能验证Integer,适用于String, Collection, Map and arrays*/
        @Size(min = 1, max = 3, message = "size在[1,3]之间")
        private String age;
    
        @Range(min = 150, max = 250, message = "range在[150,250]之间")
        private int high;
    
        @Size(min = 3,max = 5,message = "list的Size在[3,5]")
        private List<String> list;
    }
    复制代码

    验证model,以下全部验证通过:

    复制代码
        @Autowired
        private Validator validator;
        
        @RequestMapping("/demo3")
        public void demo3(){
            Demo2 demo2 = new Demo2();
            demo2.setAge("111");
            demo2.setHigh(150);
            demo2.setLength("ABCDE");
            demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});
            Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
            for (ConstraintViolation<Demo2> model : violationSet) {
                System.out.println(model.getMessage());
            }
        }
    复制代码

    4、对象级联校验

    对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部的验证:(验证Demo2示例时,可以验证Demo2的字段)

    复制代码
    @Data
    public class Demo2 {
        @Size(min = 3,max = 5,message = "list的Size在[3,5]")
        private List<String> list;
    
        @NotNull
        @Valid
        private Demo3 demo3;
    }
    
    @Data
    public class Demo3 {
        @Length(min = 5, max = 17, message = "length长度在[5,17]之间")
        private String extField;
    }
    复制代码
    级联校验:
    复制代码
        /**前面配置了快速失败返回的Bean*/
        @Autowired
        private Validator validator;
    
        @RequestMapping("/demo3")
        public void demo3(){
            Demo2 demo2 = new Demo2();
            demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});
    
            Demo3 demo3 = new Demo3();
            demo3.setExtField("22");
            demo2.setDemo3(demo3);
            Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
            for (ConstraintViolation<Demo2> model : violationSet) {
                System.out.println(model.getMessage());
            }
        }
    复制代码
    可以校验Demo3的extField字段

    5、分组校验

    结论:分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。

    有这样一种场景,新增用户信息的时候,不需要验证userId(因为系统生成);修改的时候需要验证userId,这时候可用用户到validator的分组验证功能。

    设置validator为普通验证模式("hibernate.validator.fail_fast", "false"),用到的验证GroupA、GroupB和model:

    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自带的默认分组)

    a、分组

    只验证GroupA、GroupB标记的分组:

    复制代码
    @RequestMapping("/demo5")
    public void demo5(){
    Person p = new Person();
    /**GroupA验证不通过*/
    p.setUserId(-12);
    /**GroupA验证通过*/
    //p.setUserId(12);
    p.setUserName("a");
    p.setAge(110);
    p.setSex(5);
    Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupA.class, GroupB.class);
    for (ConstraintViolation<Person> item : validate) {
    System.out.println(item);
    }
    }
    复制代码

    复制代码
        @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);
                }
            }
        }
    复制代码
    GroupA、GroupB、Default都验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='必须大于0', propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须大于0'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}

    GroupA验证通过、GroupB、Default验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}

    b、组序列

    除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证:

    指定组的序列(GroupA》GroupB》Default):

    @GroupSequence({GroupA.class, GroupB.class, Default.class})
    public interface GroupOrder {
    }

    测试demo:

    复制代码
        @RequestMapping("/demo7")
        public void demo7(){
            Person p = new Person();
            /**GroupA验证不通过*/
            //p.setUserId(-12);
            /**GroupA验证通过*/
            p.setUserId(12);
            p.setUserName("a");
            p.setAge(110);
            p.setSex(5);
            Set<ConstraintViolation<Person>> validate = validator.validate(p, GroupOrder.class);
            for (ConstraintViolation<Person> item : validate) {
                System.out.println(item);
            }
        }
    复制代码

    复制代码
        @RequestMapping("/demo8")
        public void demo8(@Validated({GroupOrder.class}) Person p, BindingResult result){
            if(result.hasErrors()){
                List<ObjectError> allErrors = result.getAllErrors();
                for (ObjectError error : allErrors) {
                    System.out.println(error);
                }
            }
        }
    复制代码
    GroupA、GroupB、Default都验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须大于0', propertyPath=userId, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须大于0'}

    GroupA验证通过、GroupB、Default验证不通过的情况:

    验证信息如下所示:

    ConstraintViolationImpl{interpolatedMessage='必须在[4,20]', propertyPath=userName, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='必须在[4,20]'}
    ConstraintViolationImpl{interpolatedMessage='性别必须在[0,2]', propertyPath=sex, rootBeanClass=class validator.demo.project.model.Person, messageTemplate='性别必须在[0,2]'}

    结论:分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。

    五、自定义验证器

    一般情况,自定义验证可以解决很多问题。但也有无法满足情况的时候,此时,我们可以实现validator的接口,自定义自己需要的验证器。

    如下所示,实现了一个自定义的大小写验证器:

    复制代码
    public enum CaseMode {
        UPPER,
        LOWER;
    }
    
    
    @Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = CheckCaseValidator.class)
    @Documented
    public @interface CheckCase {
        String message() default "";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        CaseMode value();
    }
    
    
    public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
        private CaseMode caseMode;
        public void initialize(CheckCase checkCase) {
            this.caseMode = checkCase.value();
        }
    
        public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
            if (s == null) {
                return true;
            }
    
            if (caseMode == CaseMode.UPPER) {
                return s.equals(s.toUpperCase());
            } else {
                return s.equals(s.toLowerCase());
            }
        }
    }
    复制代码

    要验证的Model:

    复制代码
        public class Demo{
            @CheckCase(value = CaseMode.LOWER,message = "userName必须是小写")
            private String userName;
    
            public String getUserName() {
                return userName;
            }
    
            public void setUserName(String userName) {
                this.userName = userName;
            }
        }
    复制代码

    validator配置:

    复制代码
        @Bean
        public Validator validator(){
            ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                    .configure()
                    .addProperty( "hibernate.validator.fail_fast", "true" )
                    .buildValidatorFactory();
            Validator validator = validatorFactory.getValidator();
    
            return validator;
        }
    复制代码

    验证测试:

    复制代码
        @RequestMapping("/demo4")
        public void demo4(){
            Demo demo = new Demo();
            demo.setUserName("userName");
            Set<ConstraintViolation<Demo>> validate = validator.validate(demo);
            for (ConstraintViolation<Demo> dem : validate) {
                System.out.println(dem.getMessage());
            }
        }
    复制代码

    输出结果:

    userName必须是小写

    六、常见的注解

      Bean Validation 中内置的 constraint     
    @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=)  被注释的元素必须符合指定的正则表达式     
    Hibernate Validator 附加的 constraint     
    @NotBlank(message =)   验证字符串非null,且长度必须大于0     
    @Email  被注释的元素必须是电子邮箱地址     
    @Length(min=,max=)  被注释的字符串的大小必须在指定的范围内     
    @NotEmpty   被注释的字符串的必须非空     
    @Range(min=,max=,message=)  被注释的元素必须在合适的范围内

    //大于0.01,不包含0.01
    @NotNull
    @DecimalMin(value = "0.01", inclusive = false)
    private Integer greaterThan;

    //大于等于0.01
    @NotNull
    @DecimalMin(value = "0.01", inclusive = true)
    private BigDecimal greatOrEqualThan;

    @Length(min = 1, max = 20, message = "message不能为空")
    //不能将Length错用成Range
    //@Range(min = 1, max = 20, message = "message不能为空")
    private String message;

    七、参考资料

    参考资料:

    • http://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html_single/#validator-gettingstarted

    展开全文
  •  在开发中经常需要写一些字段校验代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要看哪些参数...
  • 代码的管理工具就这样应运而生了CVS SVN Git2、Git的安装3、配置环境变量4、校验进入命令行模式git version如果出现了版本号 那么说明Git的安装是成功的5、Git常见的命令有哪些在桌面创建一个文件夹(用来做测试...
  • 在开发中经常需要写一些字段校验代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要看哪些参数验证...
  • 1.在开发中经常需要写一些字段校验代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要看哪些参数...
  • WIN XP蓝屏代码大全

    2013-08-08 12:29:21
    ◆错误分析:系统内存存储器奇偶校验产生错误, 通常是因为缺陷的内存(包括物理内存、二级缓存或者显卡显存)时设备驱动程序访问不存在的内存地址等原因引起的. 另外, 硬盘被病毒或者其他问题所损伤, 以出现这个停机...
  • 本章介绍基于代码来源的程序的安全运行,可以基于运行时代码在哪个URL、或代码是谁签名的限制其可以访问哪些用户资源。还介绍了定义自己的权限以及签名Java Applet。 第九章 解决的主要问题——身份验证和基于执行者...
  • 帮助程序员校验代码 2. 可以提高开发效率和程序的扩展性 @Test @Before @After 3. 可以生成文档说明 api 操作步骤: 1.选中项目/代码,右键选中Export 2....
  • 3.1.2. 签名方式............................................................................................ 8 4. 接口......................................................................................
  • 17、JSP和Servlet有哪些相同点和不同点,他们之间的联系是什么? 90 18、MVC的各个部分都有那些技术来实现?如何实现? 90 19、我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种...
  •  在开发中经常需要写一些字段校验代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要看哪些参数...
  • 多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。 5、String是最基本的数据类型吗?  基本数据类型包括byte、int、char、long、float、double、boolean和short。  java....
  • 首先,类的加载方式有哪些呢? 一、隐式加载:new 二、显示加载:loadClass 、 forName 等 类的装载过程 一、加载:通过ClassLoader 文件字节码,生成Class对象 二、链接:1、校验:检查加载的class的准确性和...
  • 创建型设计模式

    2020-11-04 11:20:05
    表示全局唯一类实现方式1. 饿汉式2. 懒汉式3. 双重校验锁4. 静态内部类5. 枚举存在哪些问题?1. 单例对OOP特性的支持不友好2. 单例对隐藏类之间的依赖关系3. 单例对代码的扩展性不友好4. 单例对代码的可测试性不...
  • 根据《阿里巴巴Java开发手册》,以下功能必须进行水平权限控制校验:ABCD A .订单详情页面。 B .类目管理后台。 C .店铺装修后台。 D .订单付款页面。 多选 6.关于数据库中NULL的描述,下列哪些说法...
  • 可以了解哪些数据库没有备份成功。 4.支持保留多少次备份。也就是删除多少次以前的历史备份。为了保证安全,可以开启当数据没有备份成功时,不删除历史备份。 5.较完善的环境检测,能准确的提醒用户那里配置出现...

空空如也

空空如也

1 2 3
收藏数 49
精华内容 19
关键字:

代码有哪些校验方式