日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Hibernate Validator 总结大全

發布時間:2023/12/16 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Hibernate Validator 总结大全 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

背景

代碼開發過程中,參數的有效性校驗是一項很繁瑣的工作, 如果參數簡單,就那么幾個參數,直接通過ifelse可以搞定,如果參數太多,比如一個大對象有100多個字段作為入參,你如何校驗呢? 仍使用ifelse就是體力活了, Hibernate Validator 是很好的選擇。

官方文檔入口: https://hibernate.org/validator/

文章示例基于6.0版本,可以參考6.0的官方文檔:https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#validator-gettingstarted

掃碼查看原文:

maven依賴

Hibernate validator 依賴

<!-- hibernate validator --> <dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.13.Final</version> </dependency> <dependency><groupId>javax.el</groupId><artifactId>javax.el-api</artifactId><version>3.0.1-b06</version> </dependency> <dependency><groupId>org.glassfish.web</groupId><artifactId>javax.el</artifactId><version>2.2.6</version> </dependency>

為了能讓示例代碼跑起來的一些必要依賴

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version><scope>provided</scope> </dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version> </dependency>

支持的校驗注解

javax.validation.constraints 包下面的校驗注解都支持,如下面這些注解,基本上見名知意, 就不一一解釋了

Max 最大值校驗 Min 最小值校驗 Range 范圍校驗,Min和Max的組合 NotBlank 不為空白字符的校驗 NotEmpty 數組、集合等不為空的校驗 NotNull 空指針校驗 Email 郵箱格式校驗 ....

下面通過示例代碼來說明校驗器常用的幾種使用方式: 簡單對象校驗、分組校驗、

簡單對象校驗

建一個需要檢驗的參數類:

@Data public class SimpleBean {@NotBlank(message = "姓名不能為空")private String name;@NotNull(message = "年齡不能為空")@Range(min = 0, max = 100, message = "年齡必須在{min}和{max}之間")private Integer age;@NotNull(message = "是否已婚不能為空")private Boolean isMarried;@NotEmpty(message = "集合不能為空")private Collection collection;@NotEmpty(message = "數組不能為空")private String[] array;@Emailprivate String email;/*真實場景下面可能還有幾十個字段省略 ... ...*/}

校驗測試

public class ValidateTest {//初始化一個校驗器工廠 private static ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()//校驗失敗是否立即返回: true-遇到一個錯誤立即返回不在往下校驗,false-校驗完所有字段才返回.failFast(false).buildValidatorFactory();Validator validator = validatorFactory.getValidator();/*** 簡單對象校驗*/@Testpublic void testSimple() {SimpleBean s=new SimpleBean();s.setAge(5);s.setName(" ");s.setEmail("email");Set<ConstraintViolation<SimpleBean>> result=validator.validate(s);System.out.println("遍歷輸出錯誤信息:");//getPropertyPath() 獲取屬性全路徑名//getMessage() 獲取校驗后的錯誤提示信息result.forEach(r-> System.out.println(r.getPropertyPath()+":"+r.getMessage()));} }

測試結果

遍歷輸出錯誤信息: email:不是一個合法的電子郵件地址 collection:集合不能為空 array:數組不能為空 name:姓名不能為空 isMarried:是否已婚不能為空

嵌套對象校驗

嵌套對象

上面是簡單對象的校驗,我們來嘗試嵌套對象的校驗,類結構如下:

|--OrgBean |----EmployeeBean |------List<PersonBean>

OrgBean.java代碼,對于嵌套對象校驗要注意, 需要在內部引用的對象上用到@Valid注解,否則不會校驗被引用對象的內部字段

@Data public class OrgBean {@NotNullprivate Integer id;@Valid //如果此處不用Valid注解,則不會去校驗EmployeeBean對象的內部字段 @NotNull(message = "employee不能為空")private EmployeeBean Employee; }

EmployeeBean.java代碼

@Data public class EmployeeBean {@Valid@NotNull(message = "person不能為空")/*** 此處用到容器元素級別的約束: List<@Valid @NotNull PersonBean> * 會校驗容器內部元素是否為null,否則為null時會跳過校驗* NotNull注解的target包含ElementType.TYPE_USE,因此NotNull可以給泛型注解*/private List<@Valid @NotNull PersonBean> people; }

PersonBean.java

@Data public class PersonBean {@NotBlank(message = "姓名不能為空")private String name;@NotNull(message = "年齡不能為空")@Range(min = 0, max = 100, message = "年齡必須在{min}和{max}之間")private Integer age;@NotNull(message = "是否已婚不能為空")private Boolean isMarried;@NotNull(message = "是否有小孩不能為空")private Boolean hasChild;@NotNull(message = "小孩個數不能為空")private Integer childCount;@NotNull(message = "是否單身不能為空")private Boolean isSingle;}

校驗測試代碼

@Test public void testNested() {PersonBean p=new PersonBean();p.setAge(30);p.setName("zhangsan");//p.setIsMarried(true);PersonBean p2=new PersonBean();p2.setAge(30);//p2.setName("zhangsan2");p2.setIsMarried(false);//p2.setHasChild(true);OrgBean org=new OrgBean();//org.setId(1);List<PersonBean> list=new ArrayList<>();list.add(p);list.add(p2);//增加一個null,測試是否會校驗元素為nulllist.add(null);EmployeeBean e=new EmployeeBean();e.setPeople(list);org.setEmployee(e);Set<ConstraintViolation<OrgBean>> result=validator.validate(org);System.out.println("遍歷輸出錯誤信息:");result.forEach(r-> System.out.println(r.getPropertyPath()+":"+r.getMessage()));}

測試結果

id:不能為null Employee.people[0].childCount:小孩個數不能為空 Employee.people[0].isSingle:是否單身不能為空 Employee.people[1].hasChild:是否有小孩不能為空 Employee.people[0].isMarried:是否已婚不能為空 Employee.people[1].name:姓名不能為空 Employee.people[1].childCount:小孩個數不能為空 Employee.people[2].<list element>:不能為null Employee.people[0].hasChild:是否有小孩不能為空 Employee.people[1].isSingle:是否單身不能為空

結果分析:
(1)可以看到打印結果中校驗的屬性名有一長串: Employee.people[0].childCount
這是由于ConstraintViolation.getPropertyPath()函數返回的是屬性的全路徑名稱。
(2)還有List元素中的值為null也進行了校驗:Employee.people[2].:不能為null
這是因為使用了容器元素級別的校驗,這種校驗器可以使用在泛型參數里面,如注解在List元素的泛型里面增加@NotNull注解: private List<@Valid @NotNull PersonBean> people;
如果沒有該注解,則list.dd(null)添加的空指針元素不會被校驗。

/*** 此處用到容器元素級別的約束 List<@Valid @NotNull PersonBean> 會校驗容器內部元素是否為null,否則為null時會跳過校驗* NotNull注解的target包含ElementType.TYPE_USE,因此NotNull可以給泛型注解*/ private List<@Valid @NotNull PersonBean> people;

Hibernate Validator 約束級別

(1)字段級別: 在字段上面添加校驗注解
本質上就是可以添加在字段上的注解,@Target({ElementType.FIELD})。

(2)屬性級別: 在方法上面添加注解,如注解在getName()方法上
本質上就是可以添加在方法上的注解,@Target({ElementType.METHOD}) 。

(3)容器級別:在容器里面添加注解
本質上就是可以添加在泛型上的注解,這個是java8新增的特性,@Target({ElementType.TYPE_USE})。
如這些類都可以支持容器級別的校驗:java.util.Iterable實現類,java.util.Map的key和values,java.util.Optional,java.util.OptionalInt,java.util.OptionalDouble,java.util.OptionalLong 等, 如:
List<@Valid @NotNull PersonBean> people;
private Map<@Valid Part, List<@Valid Manufacturer>> partManufacturers;

(4)類級別:添加在類上面的校驗注解
需要@Target({ElementType.TYPE})標注,當然如果有@Target({ElementType.TYPE_USE})也行,因為TYPE_USE包含TYPE。

分組校驗

有這樣一個需求:當People對象為已婚時(isMarried字段為true),需要校驗”配偶姓名“、”是否有小孩“等字段不能為空,當People對象為未婚時,需要校驗“是否單身”等其他字段不能為空, 這種需求可以通過分組檢驗來實現,將校驗邏輯分為兩個組,然后每次調用校驗接口時指定分組即可實現不同的校驗。 如果不管“是否已婚”都需要校驗的字段(如姓名、年齡這些字段等),則可以同時指定兩個分組。

靜態分組

靜態分組主要在類上面是使用GroupSequence注解指定一個或者多個分組,用于處理不同的校驗邏輯,我覺得這個基本上是寫死的不能更改,用不用分組區別不大,因此沒什么好說的,可以跳過直接看后面的動態分組。

@GroupSequence({ Group.UnMarried.class, Group.Married.class }) public class RentalCar extends PeopleBean {... ... }

動態分組

“未婚”和“已婚”兩個分組的代碼如下,由于分組必須是一個Class,而且有沒有任何實現只是一個標記而已,因此我可以用接口。

public interface Group {//已婚情況的分組校驗interface Married {}//未婚情況的分組校驗interface UnMarried {}}

校驗對象:People2Bean.java

@Data public class People2Bean {//不管是否已婚,都需要校驗的字段,groups里面指定兩個分組@NotBlank(message = "姓名不能為空",groups = {Group.UnMarried.class, Group.Married.class})private String name;@NotNull(message = "年齡不能為空",groups = {Group.UnMarried.class, Group.Married.class})@Range(min = 0, max = 100, message = "年齡必須在{min}和{max}之間",groups = {Group.UnMarried.class, Group.Married.class})private Integer age;@NotNull(message = "是否已婚不能為空",groups = {Group.UnMarried.class, Group.Married.class})private Boolean isMarried;//已婚需要校驗的字段@NotNull(message = "配偶姓名不能為空",groups = {Group.Married.class})private String spouseName;//已婚需要校驗的字段@NotNull(message = "是否有小孩不能為空",groups = {Group.Married.class})private Boolean hasChild;//未婚需要校驗的字段@NotNull(message = "是否單身不能為空",groups = {Group.UnMarried.class})private Boolean isSingle; }

測試代碼:通過isMarried的值來動態指定分組校驗

@Test public void testGroup() {PeopleBean p=new PeopleBean();p.setAge(30);p.setName(" ");p.setIsMarried(false);Set<ConstraintViolation<PeopleBean>> result;//通過isMarried的值來動態指定分組校驗if(p.getIsMarried()){//如果已婚,則按照已婚的分組字段result=validator.validate(p, Group.Married.class);}else{//如果未婚,則只校驗未婚的分組字段result=validator.validate(p, Group.UnMarried.class);}System.out.println("遍歷輸出錯誤信息:");result.forEach(r-> System.out.println(r.getPropertyPath()+":"+r.getMessage())); }

測試結果,可以發現,未婚校驗了isSingle字段,符合預期

遍歷輸出錯誤信息: name:姓名不能為空 isSingle:是否單身不能為空

將上述代碼中的isMarried設置為true:p.setIsMarried(false) 再次執行結果如下,也是符合預期的

遍歷輸出錯誤信息: name:姓名不能為空 hasChild:是否有小孩不能為空 spouseName:配偶姓名

動態分組優化

有沒有發現上面的分組校驗代碼實現不夠好?本來校驗我是要完全交給validator框架的,但是我還得在校驗框架之外面額外判斷isMarried再來決定校驗方式(如下代碼),這樣校驗代碼從校驗框架外泄了,不太優雅,有沒有優化的空間呢?

if(p.getIsMarried()){//如果已婚,則按照已婚的分組字段result=validator.validate(p, Group.Married.class); }else{//如果未婚,則只校驗未婚的分組字段result=validator.validate(p, Group.UnMarried.class); }

其實通過DefaultGroupSequenceProvider接口可以優化,這才是真正的動態分組校驗,在該接口實現中判斷isMarried值,來實現動態設置分組,也就是將校驗的額外判斷邏輯從校驗框架外層轉移到了校驗框架中,外層業務代碼只需要調用校驗接口即可,而無需關注具體的校驗邏輯,這樣的框架才是優秀的。

如下PeopleGroupSequenceProvider.java類實現了DefaultGroupSequenceProvider接口

public class PeopleGroupSequenceProvider implements DefaultGroupSequenceProvider<People2Bean> {@Overridepublic List<Class<?>> getValidationGroups(People2Bean bean) {List<Class<?>> defaultGroupSequence = new ArrayList<>();// 這里必須將校驗對象的類加進來,否則沒有Default分組會拋異常,這個地方還沒太弄明白,后面有時間再研究一下 defaultGroupSequence.add(People2Bean.class);if (bean != null) {Boolean isMarried=bean.getIsMarried();///System.err.println("是否已婚:" + isMarried + ",執行對應校驗邏輯");if(isMarried!=null){if(isMarried){System.err.println("是否已婚:" + isMarried + ",groups: "+Group.Married.class);defaultGroupSequence.add(Group.Married.class);}else{System.err.println("是否已婚:" + isMarried + ",groups: "+Group.UnMarried.class);defaultGroupSequence.add(Group.UnMarried.class);}}else {System.err.println("isMarried is null");defaultGroupSequence.add(Group.Married.class);defaultGroupSequence.add(Group.UnMarried.class);}}else{System.err.println("bean is null");}return defaultGroupSequence;} }

People2Bean.java類上要用到@GroupSequenceProvider注解指定一個GroupSequenceProvider

@GroupSequenceProvider(PeopleGroupSequenceProvider.class) public class People2Bean {//字段同上 //... ... }

測試代碼

@Test public void testGroupSequence(){People2Bean p=new People2Bean();p.setAge(30);p.setName(" ");System.out.println("----已婚情況:");p.setIsMarried(true);Set<ConstraintViolation<People2Bean>> result=validator.validate(p);System.out.println("遍歷輸出錯誤信息:");result.forEach(r-> System.out.println(r.getPropertyPath()+":"+r.getMessage()));System.out.println("----未婚情況:");p.setIsMarried(false);result=validator.validate(p);System.out.println("遍歷輸出錯誤信息:");result.forEach(r-> System.out.println(r.getPropertyPath()+":"+r.getMessage()));}

測試結果符合預期

----已婚情況: 遍歷輸出錯誤信息: name:姓名不能為空 spouseName:配偶姓名不能為空 hasChild:是否有小孩不能為空 ----未婚情況: 遍歷輸出錯誤信息: name:姓名不能為空 isSingle:是否單身不能為空

自定義校驗器

Hibernate中有不少約束校驗器,但是不一定能滿足你的業務,因此它還支持自定義約束校驗器,一般是一個約束注解配合一個校驗器使用,校驗器需要實現ConstraintValidator接口,然后約束注解中通過`@Constraint(validatedBy = {ByteLengthValidator.class})綁定校驗器即可。 這里我寫三個示例來說明:

自定義枚舉校驗

在開發過程中,有很多參數類型限制只能使用某些枚舉值,我們可以通過自定義的校驗器來做約束,以最簡單的性別舉例,在我國性別只有男和女,校驗注解定義如下: EnumRange.java

@Documented @Constraint(//這個配置用于綁定校驗器:EnumRangeValidatorvalidatedBy = {EnumRangeValidator.class} ) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(EnumRange.List.class) public @interface EnumRange {//自定義默認的消息模板String message() default "枚舉值不正確,范圍如下:{}";//枚舉類,用于在校驗器中限定值的范圍Class<? extends Enum> enumType();//分組 Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)@Documented//支持數組校驗public @interface List {EnumRange[] value();} }

校驗器類:EnumRangeValidator.java 實現 ConstraintValidator 接口, ConstraintValidator<EnumRange,String> 接口的第一個泛型參數綁定EnumRange注解,第二個參數綁定要校驗的值類型,這里是String。

public class EnumRangeValidator implements ConstraintValidator<EnumRange,String> {private Set<String> enumNames;private String enumNameStr;@Overridepublic void initialize(EnumRange constraintAnnotation) {Class<? extends Enum> enumType=constraintAnnotation.enumType();if(enumType==null){throw new IllegalArgumentException("EnumRange.enumType 不能為空");}try {//初始化:將枚舉值放到Set中,用于校驗Method valuesMethod = enumType.getMethod("values");Enum[] enums = (Enum[]) valuesMethod.invoke(null);enumNames = Stream.of(enums).map(Enum::name).collect(Collectors.toSet());enumNameStr = enumNames.stream().collect(Collectors.joining(","));} catch (Exception e) {throw new RuntimeException("EnumRangeValidator 初始化異常",e);}}@Overridepublic boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {if(value==null){return true;}boolean result = enumNames.contains(value);if(!result){//拿到枚舉中的message,并替換變量,這個變量是我自己約定的,//你在使用注解的message中有花括號,這里會被替換為用逗號隔開展示的枚舉值列表String message = constraintValidatorContext.getDefaultConstraintMessageTemplate().replace("{}",enumNameStr);//禁用默認值,否則會有兩條messageconstraintValidatorContext.disableDefaultConstraintViolation();//添加新的messageconstraintValidatorContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();}return result;} }

我們來定義一個性別的枚舉:當然,你還可以用其他自定義枚舉,只要是枚舉值這個校驗就就能生效

public enum SexEnum {F("女"),M("男");String desc;SexEnum(String desc){this.desc=desc;}}

被校驗的類:Person2Bean.java

@Data public class Person2Bean {@NotBlank(message = "姓名不能為空")private String name;@Range(min = 0, max = 100, message = "年齡必須在{min}和{max}之間")private Integer age;//性別用到上面的自定義注解,并指定枚舉類SexEnum,message模板里面約定變量綁定“{}” @EnumRange(enumType = SexEnum.class, message = "性別只能是如下值:{}")private String sex;}

校驗測試代碼

@Test public void testSelfDef() {Person2Bean s=new Person2Bean();//性別設置為“A",校驗應該不通過 s.setSex("A");//s.setFriendNames(Stream.of("zhangsan","李四思").collect(Collectors.toList()));Set<ConstraintViolation<Person2Bean>> result=validator.validate(s);System.out.println("遍歷輸出錯誤信息:");result.forEach(r-> System.out.println(r.getPropertyPath()+":"+r.getMessage())); }

校驗結果如下:性別設置為“A",校驗應該不通過不是枚舉值中的F和M,因此符合預期

遍歷輸出錯誤信息: sex:性別只能是如下值:F,M name:姓名不能為空

自定義字節數校驗器

參數的字段值要存入數據庫,比如某個字段用的 Oracle 的 Varchar(4) 類型,那么該字段值的不能超過4個字節,一般可能會想到應用 @Length 來校驗,但是該校驗器校驗的是字符字符串長度,即用 String.length() 來校驗的,英文字母占用的字節數與String.length()一致沒有問題,但是中文不行,根據不同的字符編碼占用的字節數不一樣,比如一個中文字符用UTF8占用3個字節,用GBK占用兩個字節,而一個英文字符不管用的什么編碼始終只占用一個字節,因此我們來創建一個字節數校驗器。

校驗注解類:ByteMaxLength.java

@Documented //綁定校驗器:ByteMaxLengthValidator @Constraint(validatedBy = {ByteMaxLengthValidator.class}) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(ByteMaxLength.List.class) public @interface ByteMaxLength {//注意這里的max是指最大字節長度,而非字符個數,對應數據庫字段類型varchar(n)中的nint max() default Integer.MAX_VALUE;String charset() default "UTF-8";Class<?>[] groups() default {};String message() default "【${validatedValue}】的字節數已經超過最大值{max}";Class<? extends Payload>[] payload() default {};@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface List {ByteMaxLength[] value();} }

校驗最大字節數的校驗器:ByteMaxLengthValidator.java ,注意里面約定了兩個綁定變量:chMax 和 enMax,分別對應中、英文的最大字符數,用于message模板中使得錯誤提示更加友好

public class ByteMaxLengthValidator implements ConstraintValidator<ByteMaxLength,String> {private int max;private Charset charset;@Overridepublic void initialize(ByteMaxLength constraintAnnotation) {max=constraintAnnotation.max();charset=Charset.forName(constraintAnnotation.charset());}@Overridepublic boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {if(value==null){return true;}int byteLength = value.getBytes(charset).length;//System.out.println("byteLength="+byteLength);boolean result = byteLength<=max;if(!result){//這里隨便用一個漢字取巧獲取每個中文字符占用該字符集的字節數int chBytes = "中".getBytes(charset).length;System.out.println("chBytes="+chBytes);//計算出最大中文字數int chMax = max/chBytes;//拿到枚舉中的message,并替換變量,這個變量是我自己約定的,//約定了兩個綁定變量:chMax 和 enMaxString message = constraintValidatorContext.getDefaultConstraintMessageTemplate().replace("{chMax}",String.valueOf(chMax)).replace("{enMax}",String.valueOf(max));//禁用默認值,否則會有兩條messageconstraintValidatorContext.disableDefaultConstraintViolation();//添加新的messageconstraintValidatorContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();}return result;} }

校驗類

@Data public class Person2Bean {/*** message里面用到了前面約定的兩個變量:chMax和enMax,* 至于${validatedValue}是框架內置的變量,用于獲取當前被校驗對象的值*/@ByteMaxLength(max=4,charset = "UTF-8", message = "姓名【${validatedValue}】全中文字符不能超過{chMax}個字,全英文字符不能超過{enMax}個字母")private String name;/*** 該注解可以用于泛型參數:List<String> ,* 這樣可以校驗List中每一個String元素的字節數是否符合要求*/private List<@ByteMaxLength(max=4,charset = "UTF-8",message = "朋友姓名【${validatedValue}】的字節數不能超過{max}")String> friendNames;@Range(min = 0, max = 100, message = "年齡必須在{min}和{max}之間")private Integer age;//@EnumRange(enumType = SexEnum.class, message = "性別只能是如下值:{}")private String sex;}

校驗測試代碼

@Test public void testSelfDef() {Person2Bean s=new Person2Bean();s.setName("張三");//s.setSex("M");s.setFriendNames(Stream.of("zhangsan","李四思","張").collect(Collectors.toList()));Set<ConstraintViolation<Person2Bean>> result=validator.validate(s);System.out.println("遍歷輸出錯誤信息:");result.forEach(r-> System.out.println(r.getPropertyPath()+":"+r.getMessage())); }

運行結果,可以發現List中的元素也可以校驗

遍歷輸出錯誤信息: name:姓名【張三】全中文字符不能超過1個字,全英文字符不能超過4個字母 friendNames[0].<list element>:朋友姓名【zhangsan】的字節數不能超過4 friendNames[1].<list element>:朋友姓名【李四思】的字節數不能超過4

由于上面用的UTF-8編碼,max=4,中文占三個字節,因此只能一個中文字符,換成GBK試一下

@ByteMaxLength(max=4,charset = "GBK", message = "姓名【${validatedValue}】全中文字符不能超過{chMax}個字,全英文字符不能超過{enMax}個字母") private String name;//可以用于校驗數組元素:List<String> private List<@ByteMaxLength(max=4,charset = "GBK",message = "朋友姓名【${validatedValue}】的字節數不能超過{max}")String> friendNames;

同樣的測試代碼發現校驗結果不一樣了:name="張三"校驗通過了,由于GBK中文值占2個字節而不是3個字節

friendNames[1].<list element>:朋友姓名【李四思】的字節數不能超過4 friendNames[0].<list element>:朋友姓名【zhangsan】的字節數不能超過4

自定義類級別的校驗器

類級別的校驗器沒什么特別的,無非是其可以注解到類上面,即由@Target({ElementType.TYPE})標注的注解。但是某些特殊場景非常有用,字段上的校驗器只能用于校驗單個字段,如果我們需要對多個字段進行特定邏輯的組合校驗就非常有用了。

下面的示例用于校驗:訂單價格==商品數量*商品價格

@OrderPrice注解:OrderPrice.java

@Documented //綁定校驗器 @Constraint(validatedBy = {OrderPriceValidator.class}) //可以發現沒有 ElementType.TYPE 該注解也能用到類上面,這是因為ElementType.TYPE_USE包含ElementType.TYPE @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(OrderPrice.List.class) public @interface OrderPrice {Class<?>[] groups() default {};String message() default "訂單價格不符合校驗規則";Class<? extends Payload>[] payload() default {};@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface List {OrderPrice[] value();} }

校驗器: OrderPriceValidator.java,注意ConstraintValidator<OrderPrice, OrderBean>第二個泛型參數為被校驗的類OrderBean

public class OrderPriceValidator implements ConstraintValidator<OrderPrice, OrderBean> {@Overridepublic void initialize(OrderPrice constraintAnnotation) {}@Overridepublic boolean isValid(OrderBean order, ConstraintValidatorContext constraintValidatorContext) {if(order==null){return true;}return order.getPrice()==order.getGoodsPrice()*order.getGoodsCount();}}

被校驗類:OrderBean.java

@Data //類上面用到自定義的校驗注解 @OrderPrice public class OrderBean {@NotBlank(message = "商品名稱不能為空")private String goodsName;@NotNull(message = "商品價格不能為空")private Double goodsPrice;@NotNull(message = "商品數量不能為空")private Integer goodsCount;@NotNull(message = "訂單價格不能為空")private Double price;@NotBlank(message = "訂單備注不能為空")private String remark;}

校驗測試代碼

@Test public void testSelfDef2() {OrderBean o=new OrderBean();o.setGoodsName("辣條");o.setGoodsCount(5);o.setGoodsPrice(1.5);o.setPrice(20.5);Set<ConstraintViolation<OrderBean>> result=validator.validate(o);System.out.println("遍歷輸出錯誤信息:");result.forEach(r-> System.out.println(r.getPropertyPath()+":"+r.getMessage())); }

測試執行結果如下:符合預期

遍歷輸出錯誤信息: :訂單價格不符合校驗規則 remark:訂單備注不能為空

EL表達式

其實在上面的示例中,可以看到在message中已經使用到了EL表達式:

@ByteMaxLength(max=4,charset = "GBK", message = "姓名【${validatedValue}】全中文字符不能超過{chMax}個字,全英文字符不能超過{enMax}個字母")private String name;

包含在${與}之間的就是EL表達式,比如這里的${validatedValue} , validatedValue是內置的變量,用于存儲當前被校驗對象的值,更復雜的用法不僅僅是取值,還可以做各種邏輯運算、內置函數調用等,如下面這些用法:

@Size(min = 2,max = 14,message = "The license plate '${validatedValue}' must be between {min} and {max} characters long" )@Min(value = 2,message = "There must be at least {value} seat${value > 1 ? 's' : ''}" )DecimalMax(value = "350",message = "The top speed ${formatter.format('%1$.2f', validatedValue)} is higher than {value}" )@DecimalMax(value = "100000", message = "Price must not be higher than ${value}")

上面有一種不包含$符號,只包含在花括號{}的表達式,這種表達式只能用于簡單的變量替換,如果沒有該變量也不會報錯,只是會被原樣輸出,而${validatedValue}這個里面的表達式如果錯了則會拋異常。

比如@Length注解有兩個變量min和max,其實像groups、payload都可以獲取到其值,也就是在message中可以獲取當前注解的所有成員變量值(除了message本身)。

public @interface Length {int min() default 0;int max() default 2147483647;String message() default "{org.hibernate.validator.constraints.Length.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};... ... }

如:

@Length(min=1,max=10,message = "字符長度請控制在{min}到{max}之間,分組校驗:{groups},消息:{message}") private String name;

上述代碼的message中{min}、{max}、{groups}最終在錯誤消息輸出時hi可以被對應的變量值替換的,但是{message}就會被原樣輸出,因為不可能在message里面獲取它自己的值。

校驗框架對EL表達式的支持對于自定義消息模板非常有用,可以使錯誤消息提示更加友好。

SpringMVC中如何使用

上面的示例代碼都是在單元測試中使用,validator類也是自己手動創建的,在spring中validator需要通過容器來創建,除了上面的maven依賴,還需在spring.xml中為校驗器配置工廠bean

<mvc:annotation-driven validator="validator"/> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"><property name="providerClass" value="org.hibernate.validator.HibernateValidator"/><property name="validationMessageSource" ref="messageSource"/> </bean><bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

然后在Controller類中方法的參數增加@Valid注解即可

@RequestMapping("/update") public String update(@Valid PersonBean person) {//TODO ... }

總結

寫到這里,上面提到的validator框架用法基本能滿足我們大多數業務場景了,我是最近在為公司寫業務代碼過程中對各種繁瑣的校驗頭痛不已,前期都是直接用ifelse搞定,后面覺得干體力活沒意思,因此通過validator框架把公司代碼現有校驗邏輯重構了一遍,非常受用,重構時比較痛苦,但是后面再使用就非常輕松了,上面這些場景都是我真實用到的,因此在這里總結一下做個筆記。

所有代碼都在如下倉庫: github-validator











總結

以上是生活随笔為你收集整理的Hibernate Validator 总结大全的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。