五年前,科技大廈 1 層 B 座。java
小明的眼睛直勾勾地盯着屏幕,雙手噼裏啪啦的敲着鍵盤。git
思考是不存在的,思考只會讓小明的速度降下來。程序員
優秀的程序員徹底不須要思考,就像不須要寫文檔和註釋同樣。github
「真是簡單的需求啊」,小明以爲有些無聊,「毫無挑戰。」web
和無數個 web 開發者同樣,小明今天作的是用戶的註冊功能。編程
首先定義一下對應的用戶註冊對象:api
public class UserRegister { /** * 名稱 */ private String name; /** * 原始密碼 */ private String password; /** * 確認密碼 */ private String password2; /** * 性別 */ private String sex; // getter & setter & toString() }
註冊時格式要求文檔也作了簡單的限制:框架
(1)name 名稱必須介於 1-32 位之間maven
(2)password 密碼必須介於 6-32 位之間ide
(3)password2 確認密碼必須和 password 保持一致
(4)sex 性別必須爲 BOY/GIRL 二者中的一個。
「這也不難」,無情的編碼機器開始瘋狂的敲打着鍵盤,不一下子基本的校驗方法就寫好了:
private void paramCheck(UserRegister userRegister) { //1. 名稱 String name = userRegister.getName(); if(name == null) { throw new IllegalArgumentException("名稱不可爲空"); } if(name.length() < 1 || name.length() > 32) { throw new IllegalArgumentException("名稱長度必須介於 1-32 之間"); } //2. 密碼 String password = userRegister.getPassword(); if(password == null) { throw new IllegalArgumentException("密碼不可爲空"); } if(password.length() < 6 || password.length() > 32) { throw new IllegalArgumentException("密碼長度必須介於 6-32 之間"); } //2.2 確認密碼 String password2 = userRegister.getPassword2(); if(!password.equals(password2)) { throw new IllegalArgumentException("確認密碼必須和密碼保持一致"); } //3. 性別 String sex = userRegister.getSex(); if(!SexEnum.BOY.getCode().equals(sex) && !SexEnum.GIRL.getCode().equals(sex)) { throw new IllegalArgumentException("性別必須指定爲 GIRL/BOY"); } }
打完收工,小明把代碼提交完畢,就早早地下班跑路了。
「小明啊,我今天簡單地看了一下你的代碼。」,項目經理看似隨意地提了一句。
小明停下了手中的工做,看向項目經理,意思是讓他繼續說下去。
「總體仍是比較嚴謹的,就是寫了太多的校驗代碼。」
「太多的校驗代碼?不校驗數據用戶亂填怎麼辦?」,小明有些不太明白。
「校驗代碼的話,有時間能夠了解一下 hibernate-validator 校驗框架。」
「能夠,我有時間看下。」
嘴上說着,小明內心一萬個不肯意。
什麼休眠框架,影響我搬磚的速度。
後來小明仍是勉爲其難的搜索了一下 hibernate-validator,看了看感受還不錯。
這個框架提供了不少內置的註解,便於平常校驗的開發,大大提高了校驗方法的可複用性。
因而,小明把本身的校驗方法改良了一下:
public class UserRegister { /** * 名稱 */ @NotNull(message = "名稱不可爲空") @Length(min = 1, max = 32, message = "名稱長度必須介於 1-32 之間") private String name; /** * 原始密碼 */ @NotNull(message = "密碼不可爲空不可爲空") @Length(min = 1, max = 32, message = "密碼長度必須介於 6-32 之間") private String password; /** * 確認密碼 */ @NotNull(message = "確認密碼不可爲空不可爲空") @Length(min = 1, max = 32, message = "確認密碼必須介於 6-32 之間") private String password2; /** * 性別 */ private String sex; }
校驗方法調整以下:
private void paramCheck2(UserRegister userRegister) { //1. 名稱 ValidateUtil.validate(userRegister); //2.2 確認密碼 String password2 = userRegister.getPassword2(); if(!userRegister.getPassword().equals(password2)) { throw new IllegalArgumentException("確認密碼必須和密碼保持一致"); } //3. 性別 String sex = userRegister.getSex(); if(!SexEnum.BOY.getCode().equals(sex) && !SexEnum.GIRL.getCode().equals(sex)) { throw new IllegalArgumentException("性別必須指定爲 GIRL/BOY"); } }
確實清爽了不少,ValidateUtil 是基於一個簡單的工具類:
public class ValidateUtil { /** * 使用hibernate的註解來進行驗證 */ private static Validator validator = Validation .byProvider(HibernateValidator.class) .configure().failFast(true) .buildValidatorFactory() .getValidator(); public static <T> void validate(T t) { Set<ConstraintViolation<T>> constraintViolations = validator.validate(t); // 拋出檢驗異常 if (constraintViolations.size() > 0) { final String msg = constraintViolations.iterator().next().getMessage(); throw new IllegalArgumentException(msg); } } }
可是小明依然以爲不滿意,sex 的校驗能夠進一步優化嗎?
答案是確定的,小明發現 hibernate-validator 支持自定義註解。
這是一個很強大的功能,優秀的框架就應該爲使用者提供更多的可能性。
因而小明實現了一個自定義註解:
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = MyEnumRangesValidator.class) public @interface MyEnumRanges { Class<? extends Enum> value(); String message() default ""; }
MyEnumRangesValidator 的實現以下:
public class MyEnumRangesValidator implements ConstraintValidator<MyEnumRanges, String> { private MyEnumRanges myEnumRanges; @Override public void initialize(MyEnumRanges constraintAnnotation) { this.myEnumRanges = constraintAnnotation; } @Override public boolean isValid(String value, ConstraintValidatorContext context) { return getEnumValues(myEnumRanges.value()).contains(value); } /** * 獲取枚舉值對應的信息 * * @param enumClass 枚舉類 * @return 枚舉說明 * @since 0.0.9 */ private List<String> getEnumValues(Class<? extends Enum> enumClass) { Enum[] enums = enumClass.getEnumConstants(); return ArrayUtil.toList(enums, new IHandler<Enum, String>() { @Override public String handle(Enum anEnum) { return anEnum.toString(); } }); } }
限制當前的字段值必須在指定的枚舉範圍內,之後全部涉及到枚舉範圍的,使用這個註解便可搞定。
而後把 @MyEnumRanges
加在 sex 字段上:
@NotNull(message = "性別不可爲空") @MyEnumRanges(message = "性別必須在 BOY/GIRL 範圍內", value = SexEnum.class) private String sex;
這樣校驗方法能夠簡化以下:
private void paramCheck3(UserRegister userRegister) { //1. 名稱 ValidateUtil.validate(userRegister); //2.2 確認密碼 String password2 = userRegister.getPassword2(); if(!userRegister.getPassword().equals(password2)) { throw new IllegalArgumentException("確認密碼必須和密碼保持一致"); } }
小明滿意的笑了笑。
可是他的笑容只是持續了一下子,由於他發現了一個不使人滿意的地方。
確認密碼這一段代碼能夠去掉嗎?
好像直接使用 hibernate-validator 框架是作不到的。
這一切令小明很痛苦,他發現框架自己確實有不少不足之處。
現在 java 最流行的 hibernate-validator 框架,可是有些場景是沒法知足的。
好比:
驗證新密碼和確認密碼是否相同。(同一對象下的不一樣屬性之間關係)
當一個屬性值知足某個條件時,才進行其餘值的參數校驗。
多個屬性值,至少有一個不能爲 null
其實,在對於多個字段的關聯關係處理時,hibernate-validator 就會比較弱。
本項目結合原有的優勢,進行這一點的功能強化。
validation-api 提供了豐富的特性定義,也同時帶來了一個問題。
實現起來,特別複雜。
然而咱們實際使用中,經常不須要這麼複雜的實現。
valid-api 提供了一套簡化不少的 api,便於用戶自行實現。
hibernate-validator 在使用中,自定義約束實現是基於註解的,針對單個屬性校驗不夠靈活。
本項目中,將屬性校驗約束和註解約束區分開,便於複用和拓展。
hibernate-validator 核心支持的是註解式編程,基於 bean 的校驗。
一個問題是針對屬性校驗不靈活,有時候針對 bean 的校驗,仍是要本身寫判斷。
本項目支持 fluent-api 進行過程式編程,同時支持註解式編程。
儘量兼顧靈活性與便利性。
因而小明花了很長時間,寫了一個校驗工具,但願能夠彌補上述工具的不足。
支持 fluent-validation
支持 jsr-303 註解,支持全部 hibenrate-validator 經常使用註解
支持 i18n
支持用戶自定義策略
支持用戶自定義註解
支持針對屬性的校驗
支持過程式編程與註解式編程
支持指定校驗生效的條件
<dependency> <groupId>com.github.houbb</groupId> <artifactId>valid-jsr</artifactId> <version>0.2.2</version> </dependency>
工具類使用:
User user = new User(); user.sex("what").password("old").password2("new"); ValidHelper.failOverThrow(user);
報錯以下:
會拋出 ValidRuntimeException 異常,異常的信息以下:
name: 值 <null> 不是預期值,password: 值 <old> 不是預期值,sex: 值 <what> 不是預期值
其中 User 的定義以下:
public class User { /** * 名稱 */ @HasNotNull({"nickName"}) private String name; /** * 暱稱 */ private String nickName; /** * 原始密碼 */ @AllEquals("password2") private String password; /** * 新密碼 */ private String password2; /** * 性別 */ @Ranges({"boy", "girl"}) private String sex; /** * 失敗類型枚舉 */ @EnumRanges(FailTypeEnum.class) private String failType; //Getter and Setter }
內置註解簡介以下:
註解 | 說明 |
---|---|
@AllEquals | 當前字段及指定字段值必須所有相等 |
@HasNotNull | 當前字段及指定字段值至少有一個不爲 null |
@EnumRanges | 當前字段值必須在枚舉屬性範圍內 |
@Ranges | 當前字段值必須在指定屬性範圍內 |
小明在設計驗證工具的時候,針對 hibernater 的不足都作了一點小小的改進。
可讓字段之間產生聯繫,以提供更增強大的功能。
每個註解都有對應的過程式方法,讓你能夠在註解式和過程式中切換自如。
內置了 @Condition
的註解生效條件,讓註解生效更加靈活。
小明擡頭看了看牆上的鐘,夜已經太深了,百聞不如一見,感興趣的小夥伴能夠本身去感覺一下:
這個開源工具是平常工做中不想寫太多校驗方法的產物,還處於初期階段,還有不少須要改進的地方。
不過,但願你能喜歡。
我是老馬,期待與你的下次重逢。