一、參數(shù)校驗(yàn)
在開發(fā)中經(jīng)常需要寫一些字段校驗(yàn)的代碼,比如字段非空,字段長(zhǎng)度限制,郵箱格式驗(yàn)證等等,寫這些與業(yè)務(wù)邏輯關(guān)系不大的代碼個(gè)人感覺有兩個(gè)麻煩:
- 驗(yàn)證代碼繁瑣,重復(fù)勞動(dòng)
- 方法內(nèi)代碼顯得冗長(zhǎng)
- 每次要看哪些參數(shù)驗(yàn)證是否完整,需要去翻閱驗(yàn)證邏輯代碼
hibernate validator(官方文檔)提供了一套比較完善、便捷的驗(yàn)證實(shí)現(xiàn)方式。
spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依賴。
二、hibernate validator校驗(yàn)demo
先來看一個(gè)簡(jiǎn)單的demo,添加了Validator的注解:
1
2
3
|
import org.hibernate.validator.constraints.NotBlank; import javax.validation.constraints.AssertFalse; import javax.validation.constraints.Pattern; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@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; /** * 如果是空,則不校驗(yàn),如果不為空,則校驗(yàn) */ @Pattern (regexp= "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" ,message= "出生日期格式不正確" ) private String birthday; } |
POST接口驗(yàn)證,BindingResult是驗(yàn)證不通過的結(jié)果集合:
1
2
3
4
5
6
7
8
|
@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請(qǐng)求傳入的參數(shù):{"userName":"dd","age":120,"isFalse":true,"birthday":"21010-21-12"}
輸出結(jié)果:
出生日期格式不正確
必須為false
年齡不正確
參數(shù)驗(yàn)證非常方便,字段上注解+驗(yàn)證不通過提示信息即可代替手寫一大堆的非空和字段限制驗(yàn)證代碼。下面深入了解下參數(shù)校驗(yàn)的玩法。
三、hibernate的校驗(yàn)?zāi)J?/strong>
細(xì)心的讀者肯定發(fā)現(xiàn)了:上面例子中一次性返回了所有驗(yàn)證不通過的集合,通常按順序驗(yàn)證到第一個(gè)字段不符合驗(yàn)證要求時(shí),就可以直接拒絕請(qǐng)求了。Hibernate Validator有以下兩種驗(yàn)證模式:
1、普通模式(默認(rèn)是這個(gè)模式)
普通模式(會(huì)校驗(yàn)完所有的屬性,然后返回所有的驗(yàn)證失敗信息)
2、快速失敗返回模式
快速失敗返回模式(只要有一個(gè)驗(yàn)證失敗,則返回)
兩種驗(yàn)證模式配置方式:(參考官方文檔)
failFast:true 快速失敗返回模式 false 普通模式
1
2
3
4
5
|
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator. class ) .configure() .failFast( true ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); |
和 (hibernate.validator.fail_fast:true 快速失敗返回模式 false 普通模式)
1
2
3
4
5
|
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator. class ) .configure() .addProperty( "hibernate.validator.fail_fast" , "true" ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); |
四、hibernate的兩種校驗(yàn)
配置hibernate Validator為快速失敗返回模式:
1
2
3
4
5
6
7
8
9
10
11
12
|
@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、請(qǐng)求參數(shù)校驗(yàn)
如demo里示例的,驗(yàn)證請(qǐng)求參數(shù)時(shí),在@RequestBody DemoModel demo之間加注解 @Valid,然后后面加BindindResult即可;多個(gè)參數(shù)的,可以加多個(gè)@Valid和BindingResult,如:
1
2
|
public void test()( @RequestBody @Valid DemoModel demo, BindingResult result) public void test()( @RequestBody @Valid DemoModel demo, BindingResult result, @RequestBody @Valid DemoModel demo2, BindingResult result2) |
1
2
3
4
5
6
7
8
|
@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參數(shù)校驗(yàn)(@RequestParam參數(shù)校驗(yàn))
使用校驗(yàn)bean的方式,沒有辦法校驗(yàn)RequestParam的內(nèi)容,一般在處理Get請(qǐng)求(或參數(shù)比較少)的時(shí)候,會(huì)使用下面這樣的代碼:
1
2
3
4
|
@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注解,對(duì)RequestParam對(duì)應(yīng)的參數(shù)進(jìn)行注解,是無效的,需要使用@Validated注解來使得驗(yàn)證生效。如下所示:
a.此時(shí)需要使用MethodValidationPostProcessor
的Bean:
1
2
3
4
5
|
@Bean public MethodValidationPostProcessor methodValidationPostProcessor() { /**默認(rèn)是普通模式,會(huì)返回所有的驗(yàn)證不通過信息集合*/ return new MethodValidationPostProcessor(); } |
或 可對(duì)MethodValidationPostProcessor
進(jìn)行設(shè)置Validator(因?yàn)榇藭r(shí)不是用的Validator進(jìn)行驗(yàn)證,Validator的配置不起作用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Bean public MethodValidationPostProcessor methodValidationPostProcessor() { MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor(); /**設(shè)置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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@RequestMapping ( "/validation" ) @RestController @Validated public class ValidationController { /**如果只有少數(shù)對(duì)象,直接把參數(shù)寫到Controller層,然后在Controller層進(jìn)行驗(yàn)證就可以了。*/ @RequestMapping (value = "/demo3" , method = RequestMethod.GET) public void demo3( @Range (min = 1 , max = 9 , message = "年級(jí)只能從1-9" ) @RequestParam (name = "grade" , required = true ) int grade, @Min (value = 1 , message = "班級(jí)最小只能1" ) @Max (value = 99 , message = "班級(jí)最大只能99" ) @RequestParam (name = "classroom" , required = true ) int classroom) { System.out.println(grade + "," + classroom); } } |
c.返回驗(yàn)證信息提示
可以看到:驗(yàn)證不通過時(shí),拋出了ConstraintViolationException異常,使用同一捕獲異常處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@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) { /**打印驗(yàn)證不通過的信息*/ System.out.println(item.getMessage()); } } return "bad request, " ; } } |
d.驗(yàn)證
瀏覽器服務(wù)請(qǐng)求地址:http://localhost:8080/validation/demo3?grade=18&classroom=888
沒有配置快速失敗返回的MethodValidationPostProcessor 時(shí)輸出信息如下:
年級(jí)只能從1-9
班級(jí)最大只能99
配置了快速失敗返回的MethodValidationPostProcessor 時(shí)輸出信息如下:
年級(jí)只能從1-9
瀏覽器服務(wù)請(qǐng)求地址:http://localhost:8080/validation/demo3?grade=0&classroom=0
沒有配置快速失敗返回的MethodValidationPostProcessor 時(shí)輸出信息如下:
年級(jí)只能從1-9
班級(jí)最小只能1
配置了快速失敗返回的MethodValidationPostProcessor 時(shí)輸出信息如下:
年級(jí)只能從1-9
3、model校驗(yàn)
待校驗(yàn)的model:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Data public class Demo2 { @Length (min = 5 , max = 17 , message = "length長(zhǎng)度在[5,17]之間" ) private String length; /**@Size不能驗(yàn)證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; } |
驗(yàn)證model,以下全部驗(yàn)證通過:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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、對(duì)象級(jí)聯(lián)校驗(yàn)
對(duì)象內(nèi)部包含另一個(gè)對(duì)象作為屬性,屬性上加@Valid,可以驗(yàn)證作為屬性的對(duì)象內(nèi)部的驗(yàn)證:(驗(yàn)證Demo2示例時(shí),可以驗(yàn)證Demo2的字段)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@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長(zhǎng)度在[5,17]之間" ) private String extField; } |
級(jí)聯(lián)校驗(yàn):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**前面配置了快速失敗返回的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()); } } |
可以校驗(yàn)Demo3的extField字段。
5、分組校驗(yàn)
結(jié)論:分組順序校驗(yàn)時(shí),按指定的分組先后順序進(jìn)行驗(yàn)證,前面的驗(yàn)證不通過,后面的分組就不行驗(yàn)證。
有這樣一種場(chǎng)景,新增用戶信息的時(shí)候,不需要驗(yàn)證userId(因?yàn)橄到y(tǒng)生成);修改的時(shí)候需要驗(yàn)證userId,這時(shí)候可用用戶到validator的分組驗(yàn)證功能。
設(shè)置validator為普通驗(yàn)證模式("hibernate.validator.fail_fast", "false"),用到的驗(yàn)證GroupA、GroupB和model:
1
2
3
4
5
|
GroupA、GroupB: public interface GroupA { } public interface GroupB { } |
驗(yàn)證model:Person
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@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個(gè)分組分別驗(yàn)證字段如下:
- GroupA驗(yàn)證字段userId;
- GroupB驗(yàn)證字段userName、sex;
- Default驗(yàn)證字段age(Default是Validator自帶的默認(rèn)分組)
a、分組
只驗(yàn)證GroupA、GroupB標(biāo)記的分組:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@RequestMapping ( "/demo5" ) public void demo5(){ Person p = new Person(); /**GroupA驗(yàn)證不通過*/ p.setUserId(- 12 ); /**GroupA驗(yàn)證通過*/ //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); } } |
或
1
2
3
4
5
6
7
8
9
|
@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都驗(yàn)證不通過的情況:
驗(yàn)證信息如下所示:
1
2
3
|
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驗(yàn)證通過、GroupB、Default驗(yàn)證不通過的情況:
驗(yàn)證信息如下所示:
1
2
|
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、組序列
除了按組指定是否驗(yàn)證之外,還可以指定組的驗(yàn)證順序,前面組驗(yàn)證不通過的,后面組不進(jìn)行驗(yàn)證:
指定組的序列(GroupA》GroupB》Default):
1
2
3
|
@GroupSequence ({GroupA. class , GroupB. class , Default. class }) public interface GroupOrder { } |
測(cè)試demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@RequestMapping ( "/demo7" ) public void demo7(){ Person p = new Person(); /**GroupA驗(yàn)證不通過*/ //p.setUserId(-12); /**GroupA驗(yàn)證通過*/ 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); } } |
或
1
2
3
4
5
6
7
8
9
|
@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都驗(yàn)證不通過的情況:
驗(yàn)證信息如下所示:
1
|
ConstraintViolationImpl{interpolatedMessage= '必須大于0' , propertyPath=userId, rootBeanClass= class validator.demo.project.model.Person, messageTemplate= '必須大于0' } |
GroupA驗(yàn)證通過、GroupB、Default驗(yàn)證不通過的情況:
驗(yàn)證信息如下所示:
1
2
|
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]' } |
結(jié)論:分組順序校驗(yàn)時(shí),按指定的分組先后順序進(jìn)行驗(yàn)證,前面的驗(yàn)證不通過,后面的分組就不行驗(yàn)證。
五、自定義驗(yàn)證器
一般情況,自定義驗(yàn)證可以解決很多問題。但也有無法滿足情況的時(shí)候,此時(shí),我們可以實(shí)現(xiàn)validator的接口,自定義自己需要的驗(yàn)證器。
如下所示,實(shí)現(xiàn)了一個(gè)自定義的大小寫驗(yàn)證器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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()); } } } |
要驗(yàn)證的Model:
1
2
3
4
5
6
7
8
9
10
|
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配置:
1
2
3
4
5
6
7
8
9
|
@Bean public Validator validator(){ ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator. class ) .configure() .addProperty( "hibernate.validator.fail_fast" , "true" ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); return validator; } |
驗(yàn)證測(cè)試:
1
2
3
4
5
6
7
8
9
|
@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()); } } |
輸出結(jié)果:
userName必須是小寫
六、常見的注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
Bean Validation 中內(nèi)置的 constraint @Null 被注釋的元素必須為 null @NotNull 被注釋的元素必須不為 null @AssertTrue 被注釋的元素必須為 true @AssertFalse 被注釋的元素必須為 false @Min (value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值 @Max (value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值 @DecimalMin (value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值 @DecimalMax (value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值 @Size (max=, min=) 被注釋的元素的大小必須在指定的范圍內(nèi) @Digits (integer, fraction) 被注釋的元素必須是一個(gè)數(shù)字,其值必須在可接受的范圍內(nèi) @Past 被注釋的元素必須是一個(gè)過去的日期 @Future 被注釋的元素必須是一個(gè)將來的日期 @Pattern (regex=,flag=) 被注釋的元素必須符合指定的正則表達(dá)式 Hibernate Validator 附加的 constraint @NotBlank (message =) 驗(yàn)證字符串非 null ,且長(zhǎng)度必須大于 0 @Email 被注釋的元素必須是電子郵箱地址 @Length (min=,max=) 被注釋的字符串的大小必須在指定的范圍內(nèi) @NotEmpty 被注釋的字符串的必須非空 @Range (min=,max=,message=) 被注釋的元素必須在合適的范圍內(nèi) //大于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錯(cuò)用成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
原文鏈接:https://www.cnblogs.com/mr-yang-localhost/p/7812038.html