改進 hibernate-validator,新一代校驗框架 validator 使用介紹 v0.4

2023-03-20 18:01:13

專案介紹

java 開發中,引數校驗是非常常見的需求。但是 hibernate-validator 在使用過程中,依然會存在一些問題。

validator 在 hibernate-validator 等校驗工具之上,做了一些改進,使其使用更加便捷優雅,進一步提升工作效率。

變更紀錄檔

變更紀錄檔

特性

  • 支援 fluent-validation

  • 支援 jsr-303 註解

  • 支援 i18n

  • 支援使用者自定義策略

  • 支援使用者自定義註解

  • 支援針對屬性的校驗

  • 支援程式式程式設計與註解式程式設計

  • 支援指定校驗生效的條件

創作目的

hibernate-validator 無法滿足的場景

如今 java 最流行的 hibernate-validator 框架,但是有些場景是無法滿足的。

比如:

  1. 驗證新密碼和確認密碼是否相同。(同一物件下的不同屬性之間關係)

  2. 當一個屬性值滿足某個條件時,才進行其他值的引數校驗。

  3. 多個屬性值,至少有一個不能為 null

其實,在對於多個欄位的關聯關係處理時,hibernate-validator 就會比較弱

本專案結合原有的優點,進行這一點的功能強化。

validation-api 過於複雜

validation-api 提供了豐富的特性定義,也同時帶來了一個問題。

實現起來,特別複雜。

然而我們實際使用中,常常不需要這麼複雜的實現。

validator-api 提供了一套簡化很多的 api,便於使用者自行實現。

自定義缺乏靈活性

hibernate-validator 在使用中,自定義約束實現是基於註解的,針對單個屬性校驗不夠靈活。

本專案中,將屬性校驗約束和註解約束區分開,便於複用和拓展

程式式程式設計 vs 註解式程式設計

hibernate-validator 核心支援的是註解式程式設計,基於 bean 的校驗。

一個問題是針對屬性校驗不靈活,有時候針對 bean 的校驗,還是要自己寫判斷。

本專案支援 fluent-api 進行程式式程式設計,同時支援註解式程式設計。

儘可能兼顧靈活性與便利性。

快速開始

準備工作

JDK1.7+

Maven 3.X+

maven 引入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>validator-core</artifactId>
    <version>0.4.0</version>
</dependency>

快速入門

定義物件

第一步,我們定義一個常見的 java bean 物件,可以指定內建的註解。

支援 jsr-303 註解和 hibernate-validator 的註解。

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 & setter

}

ValidHelper 工具方法

ValidHelper 作為統一封裝的工具類,提供了 java bean 校驗常見的方法。

方法列表:

序號 方法 返回值 說明
1 failOver(Object object) IResult 全部驗證後返回
2 failFast(Object object) IResult 快速驗證後返回
3 failOverThrow(Object object) void 全部驗證後返回-未通過丟擲 ValidRuntimeException 異常
4 failFastThrow(Object object) void 快速驗證後返回-未通過丟擲 ValidRuntimeException 異常

使用起來很簡單,我們以 failFast 為例:

// 物件定義
User user = new User();
user.sex("what").password("old").password2("new");

// 呼叫方法
IResult result = ValidHelper.failFast(user);

結果:

DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='name: 值 <null> 不是預期值', value=null, constraint='HasNotNullConstraint', expectValue='', fieldName='name'}], allList=null}
  • IResult 方法說明

返回值實現預設為 DefaultResult,介面 IResult 屬性如下:

public interface IResult {

    /**
     * 是否全部通過驗證
     * @return 是否
     * @since 0.1.0
     */
    boolean pass();

    /**
     * 未通過的列表資訊
     * @return 驗證結果
     * @since 0.1.0
     */
    List<IConstraintResult> notPassList();

    /**
     * 所有的驗證結果列表
     * @return 所有的驗證結果
     * @since 0.1.0
     */
    List<IConstraintResult> allList();

    /**
     * 輸出資訊到控臺
     * (1)主要是為了方便調整
     * (2)該功能其實可以做增強,比如輸出到檔案/資料庫等等。
     * @return this
     * @since 0.0.6
     */
    IResult print();

    /**
     * 對於未通過的資訊,
     * (1)未通過的界定。
     *  {@link IConstraintResult#pass()} 為 false
     *
     * (2)內容資訊
     * 丟擲執行時異常 {@link com.github.houbb.validator.api.exception.ValidRuntimeException},異常資訊為 {@link IConstraintResult#message()} 訊息
     * (3)內容限定
     *  為了避免異常內容過多,只丟擲第一條即可。
     *  (4)改方法的增強空間
     *  4.1 可以指定什麼情況下丟擲異常
     *  4.2 丟擲異常的資訊和類別
     *
     * @return this
     * @since 0.0.6
     */
    IResult throwsEx();

}

註解說明

java bean 的校驗,基於註解是比較方便的。和 hibernate-validator 使用類似,這裡介紹下常見的註解。

內建約束註解

內建註解如下:

序號 註解 value() 說明
1 @AllEquals String[] 當前欄位及其指定的欄位 全部相等
2 @EnumRanges Class<? extends Enum> 當前欄位必須在列舉值指定的範圍內
3 @HasNotNull String[] 當前欄位及其指定的欄位 至少有一個不為 null
4 @Ranges String[] 當前欄位必須在指定的範圍內

JSR-303 + hibernate-validator 約束註解支援

序號 註解 說明
1 @AssertTrue 為 true 約束條件
2 @AssertFalse 為 false 約束條件
3 @Null 為 null 約束條件
4 @NotNull 不為 null 約束條件
5 @Past 是否在當前時間之前約束條件
6 @Future 是否在當前時間之後約束條件
7 @Pattern 正規表示式約束條件
8 @Size 在指定範圍內的約束條件
9 @Digits 數位位數的約束條件
10 @DecimalMax 最大數位的約束條件
11 @DecimalMin 最小數位的約束條件
12 @Min 最小的約束條件
13 @Max 最大的約束條件
13 @NotBlank 不能為空格的約束條件
14 @NotEmpty 不能為空的約束條件
15 @Length 長度的約束條件
16 @CNPJ CNPJ 約束條件
17 @CPF CPF 約束條件
18 @URL URL 約束條件
18 @Email Email 約束條件
19 @UniqueElements 元素唯一約束條件
20 @Range 指定範圍元素約束條件

條件註解

說明

有時候我們需要根據不同的引數,進行不同的限制條件。

比如新建時使用者 id 不需要傳入,但是修改時 id 必填。

如果是傳統的 hibernate-validator 處理就會比較麻煩,此處引入條件註解。

內建註解

序號 註解 說明
1 @EqualsCondition 等於指定值的條件
2 @NotEqualsCondition 不等於指定值的條件
3 @AlwaysTrueCondition 永遠生效的條件
4 @AlwaysFalseCondition 永遠不生效的條件

使用

使用起來也不難,下面的效果如下:

  1. operType == 'create' 時,name 的校驗才會生效。
  2. operType != 'create' 時,id 的校驗才會生效。

其他使用方式保持不變。

public class ConditionUser {

    /**
     * 操作型別
     */
    @Ranges({"create", "edit"})
    private String operType;

    /**
     * 新建時,name 必填
     */
    @EqualsCondition(value = "create", fieldName = "operType")
    @Size(min = 3)
    @NotNull
    private String name;

    /**
     * 不是新建時, id 欄位必填
     */
    @NotEqualsCondition(value = "create", fieldName = "operType")
    @Size(min = 16)
    private String id;
    
    //getter & setter
}

過程式介面

說明

日常開發中,我們都很喜歡使用註解對 java bean 進行校驗。

但是這回導致我們定義的單個屬性校驗無法複用。

所以專案中的單個屬性校驗和註解是一一對應的,為了便於複用。

ValidHelper 方法

ValidHelper 作為統一封裝的工具類,提供單個方法校驗常見的方法。

和 java bean 類似,方法列表:

序號 方法 返回值 說明
1 failOver(Object object, IConstraint constraint) IResult 全部驗證後返回
2 failFast(Object object, IConstraint constraint) IResult 快速驗證後返回
3 failOverThrow(Object object, IConstraint constraint) void 全部驗證後返回-未通過丟擲 ValidRuntimeException 異常
4 failFastThrow(Object object, IConstraint constraint) void 快速驗證後返回-未通過丟擲 ValidRuntimeException 異常

使用例子

用法和 bean 的類似,只是入參多了第二個約束條件。

IResult result = ValidHelper.failFast("", Constraints.notEmptyConstraint());

IConstraint 對應關係

註解和常見的介面方法一一對應,所有的約束方法在 Constraints 工具類中。

序號 註解 說明 對應方法
1 @AssertTrue 為 true 約束條件 assertTrueConstraint
2 @AssertFalse 為 false 約束條件 assertFalseConstraint
3 @Null 為 null 約束條件 nullConstraint
4 @NotNull 不為 null 約束條件 notNullConstraint
5 @Past 是否在當前時間之前約束條件 pastConstraint
6 @Future 是否在當前時間之後約束條件 futureConstraint
7 @Pattern 正規表示式約束條件 patternConstraint
8 @Size 在指定範圍內的約束條件 sizeConstraint
9 @Digits 數位位數的約束條件 digitsConstraint
10 @DecimalMax 最大數位的約束條件 decimalMaxConstraint
11 @DecimalMin 最小數位的約束條件 decimalMinConstraint
12 @Min 最小的約束條件 minConstraint
13 @Max 最大的約束條件 maxConstraint
13 @NotBlank 不能為空格的約束條件 notBlankConstraint
14 @NotEmpty 不能為空的約束條件 notEmptyConstraint
15 @Length 長度的約束條件 lengthConstraint
16 @CNPJ CNPJ 約束條件 cnpjConstraint
17 @CPF CPF 約束條件 cpfConstraint
18 @URL URL 約束條件 urlConstraint
18 @Email Email 約束條件 emailConstraint
19 @UniqueElements 元素唯一約束條件 uniqueElementsConstraint
20 @Range 指定範圍元素約束條件 rangeConstraint
21 @AllEquals 當前欄位及其指定的欄位 全部相等 allEqualsConstraint
22 @EnumRanges 當前欄位必須在列舉值指定的範圍內 enumRangesConstraint
23 @HasNotNull 當前欄位及其指定的欄位 至少有一個不為 null hasNotNullConstraint
24 @Ranges 當前欄位必須在指定的範圍內 rangesConstraint

條件註解

註解和常見的介面方法一一對應,所有的約束方法在 Conditions 工具類中。

序號 註解 說明 對應方法
1 @EqualsCondition 等於指定值的條件 equalsCondition
2 @NotEqualsCondition 不等於指定值的條件 notEqualsCondition
3 @AlwaysTrueCondition 永遠生效的條件 alwaysTrueCondition
4 @AlwaysFalseCondition 永遠不生效的條件 alwaysFalseCondition

註解自定義

說明

內建的註解,自然無法滿足所有的場景。

本專案中,約束和條件註解都是支援自定義的。

約束註解 @Constraint

所有系統的內建註解都可以作為學習的例子。

定義註解

@AllEquals 為例,核心的部分在 @Constraint(AtAllEqualsConstraint.class)

我們在 AtAllEqualsConstraint 中實現具體的約束邏輯。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(AtAllEqualsConstraint.class)
public @interface AllEquals {

    /**
     * 當前欄位及其指定的欄位 全部相等
     * 1. 欄位型別及其他欄位相同
     * @return 指定的欄位列表
     */
    String[] value();

    /**
     * 提示訊息
     * @return 錯誤提示
     */
    String message() default "";

    /**
     * 分組資訊
     * @return 分組類
     * @since 0.1.2
     */
    Class[] group() default {};

}

實現邏輯

推薦直接繼承 AbstractAnnotationConstraint<A>,實現對應的邏輯即可。

public class AtAllEqualsConstraint extends AbstractAnnotationConstraint<AllEquals> {

    @Override
    protected IConstraint buildConstraint(AllEquals annotation) {
        return Constraints.allEqualsConstraint(annotation.value());
    }

}

條件註解 @Condition

所有系統的內建註解都可以作為學習的例子。

定義註解

@AlwaysTrueCondition 為例,核心的部分在 @Condition(AtAlwaysTrueCondition.class)

我們在 AtAlwaysTrueCondition 中實現具體的約束邏輯。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Condition(AtAlwaysTrueCondition.class)
public @interface AlwaysTrueCondition {
}

實現邏輯

推薦直接繼承 AbstractAnnotationCondition<A>,實現對應的邏輯即可。

public class AtAlwaysTrueCondition extends AbstractAnnotationCondition<AlwaysTrueCondition> {

    @Override
    protected ICondition buildCondition(AlwaysTrueCondition annotation) {
        return Conditions.alwaysTrueCondition();
    }

}

開源地址

為了便於大家學習使用,目前校驗框架已開源。

歡迎大家 fork+star,鼓勵一下老馬~

validator