springboot使用hibernate validator校验

目录

一、参数校验

二、hibernate validator校验demo

三、hibernate的校验模式

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

2、快速失败返回模式

四、hibernate的两种校验

1、请求参数校验

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

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

b.方法所在的Controller上加注解@Validated

c.返回验证信息提示

d.验证

3、model校验

4、对象级联校验

5、分组校验

a、分组

b、组序列

五、自定义验证器

六、常见的注解

七、参考资料

返回目录

一、参数校验

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

验证代码繁琐,重复劳动

方法内代码显得冗长

每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码

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

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

返回目录

二、hibernate validator校验demo

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

importorg.hibernate.validator.constraints.NotBlank;

importjavax.validation.constraints.AssertFalse;

importjavax.validation.constraints.Pattern;

@Getter

@Setter

@NoArgsConstructorpublicclass 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")

    publicvoid 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为快速失败返回模式:

@Configurationpublicclass 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")

    publicvoid 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)

    publicvoiddemo3(@RequestParam(name = "grade", required =true) intgrade,@RequestParam(name = "classroom", required =true)int classroom) {

        System.out.println(grade + "," + classroom);

    }

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

返回目录

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

    @Bean

    publicMethodValidationPostProcessor methodValidationPostProcessor() {

/**默认是普通模式,会返回所有的验证不通过信息集合*/

returnnew MethodValidationPostProcessor();

    }

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

    @Bean

    public MethodValidationPostProcessor methodValidationPostProcessor() {

        MethodValidationPostProcessor postProcessor =newMethodValidationPostProcessor();

/**设置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@Validatedpublicclass ValidationController {

    /**如果只有少数对象,直接把参数写到Controller层,然后在Controller层进行验证就可以了。*/    @RequestMapping(value = "/demo3", method = RequestMethod.GET)

    publicvoiddemo3(@Range(min = 1, max = 9, message = "年级只能从1-9")@RequestParam(name= "grade", required =true)

                      intgrade,@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

@Componentpublicclass GlobalExceptionHandler {

    @ExceptionHandler

    @ResponseBody

    @ResponseStatus(HttpStatus.BAD_REQUEST)

    public String handle(ValidationException exception) {

        if(exceptioninstanceof ConstraintViolationException){

            ConstraintViolationException exs = (ConstraintViolationException) exception;

            Set> violations = exs.getConstraintViolations();

            for(ConstraintViolationitem : 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:

@Datapublicclass 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]之间")

    privateint high;

    @Size(min = 3,max = 5,message = "list的Size在[3,5]")

    privateList list;

}

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

    @Autowired

    private Validator validator;


    @RequestMapping("/demo3")

    publicvoid demo3(){

        Demo2 demo2 =new Demo2();

        demo2.setAge("111");

        demo2.setHigh(150);

        demo2.setLength("ABCDE");

        demo2.setList(newArrayList(){{add("111");add("222");add("333");}});

        Set> violationSet = validator.validate(demo2);

        for(ConstraintViolation model : violationSet) {

            System.out.println(model.getMessage());

        }

    }

返回目录

4、对象级联校验

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

@Datapublicclass Demo2 {

    @Size(min = 3,max = 5,message = "list的Size在[3,5]")

    privateList list;

    @NotNull

    @Valid

    private Demo3 demo3;

}

@Datapublicclass Demo3 {

    @Length(min = 5, max = 17, message = "length长度在[5,17]之间")

    private String extField;

}

级联校验:

/**前面配置了快速失败返回的Bean*/    @Autowired

    private Validator validator;

    @RequestMapping("/demo3")

    publicvoid demo3(){

        Demo2 demo2 =new Demo2();        demo2.setList(newArrayList(){{add("111");add("222");add("333");}});

        Demo3 demo3 =new Demo3();

        demo3.setExtField("22");

        demo2.setDemo3(demo3);

        Set> violationSet = validator.validate(demo2);

        for(ConstraintViolation model : violationSet) {

            System.out.println(model.getMessage());

        }

    }

可以校验Demo3的extField字段。

返回目录

5、分组校验

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

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

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

GroupA、GroupB:

publicinterface GroupA {

}publicinterface GroupB {

}

验证model:Person

@Datapublicclass 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> validate = validator.validate(p, GroupA.class, GroupB.class);

for (ConstraintViolation item : validate) {

System.out.println(item);

}

}

@RequestMapping("/demo6")

    publicvoiddemo6(@Validated({GroupA.class, GroupB.class}) Person p, BindingResult result){

        if(result.hasErrors()){

            List 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})publicinterface GroupOrder {

}

测试demo:

@RequestMapping("/demo7")

    publicvoid demo7(){

        Person p =new Person();

        /**GroupA验证不通过*///p.setUserId(-12);/**GroupA验证通过*/        p.setUserId(12);

        p.setUserName("a");

        p.setAge(110);

        p.setSex(5);

        Set> validate = validator.validate(p, GroupOrder.class);

        for(ConstraintViolation item : validate) {

            System.out.println(item);

        }

    }

@RequestMapping("/demo8")

    publicvoiddemo8(@Validated({GroupOrder.class}) Person p, BindingResult result){

        if(result.hasErrors()){

            List 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的接口,自定义自己需要的验证器。

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

publicenum CaseMode {

    UPPER,

    LOWER;

}

@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })

@Retention(RetentionPolicy.RUNTIME)

@Constraint(validatedBy = CheckCaseValidator.class)

@Documentedpublic@interface CheckCase {

    String message() default"";

    Class[] groups()default {};

    Class[] payload()default {};

    CaseMode value();

}publicclassCheckCaseValidatorimplementsConstraintValidator {

    private CaseMode caseMode;

    publicvoid initialize(CheckCase checkCase) {

        this.caseMode = checkCase.value();

    }

    publicboolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {

        if(s ==null) {

            returntrue;

        }

        if(caseMode == CaseMode.UPPER) {

            return s.equals(s.toUpperCase());

        } else {

            return s.equals(s.toLowerCase());

        }

    }

}

要验证的Model:

publicclass Demo{

        @CheckCase(value = CaseMode.LOWER,message = "userName必须是小写")

        private String userName;

        public String getUserName() {

            return userName;

        }

        publicvoid 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")

    publicvoid demo4(){

        Demo demo =new Demo();

        demo.setUserName("userName");

        Set> validate = validator.validate(demo);

        for(ConstraintViolation 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容