java bean 屬性驗證框架 valid

項目介紹

java 開發中,參數校驗是非常常見的需求。

但是 hibernate-validator 在使用過程中,依然會存在一些問題。

特性

  • 支持 fluent-validation

  • 支持 jsr-303 註解

  • 支持 i18n

  • 支持用戶自定義策略

  • 支持用戶自定義註解

開源地址

valid

創作目的

hibernate-validator 無法滿足的場景

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

比如:

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

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

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

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

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

validation-api 過於複雜

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

實現起來,特別複雜。

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

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

自定義缺乏靈活性

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

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

過程式編程 vs 註解式編程

hibernate-validator 核心支持的是註解式編程,基於 bean 的校驗。

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

本項目支持 fluent-api 進行過程式編程,同時支持註解式編程。

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

項目模塊說明

模塊名稱 說明
valid-api 核心 api 及註解定義
valid-core 針對 valid-api 的核心實現
valid-jsr 針對 JSR-303 標準註解的實現
valid-test 測試代碼模塊

依賴說明

valid-core 默認引入 valid-api

valid-jsr 默認引入 valid-core

快速開始

準備工作

JDK1.7+

Maven 3.X+

maven 引入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>valid-jsr</artifactId>
    <version>0.1.2</version>
</dependency>

例子

我們直接利用 jsr 內置的約束類:

public void helloValidTest() {
    IResult result = ValidBs.on(null, JsrConstraints.notNullConstraint())
            .result()
            .print();
    Assert.assertFalse(result.pass());
}

對應日誌輸出爲:

DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='預期值爲 <not null>,實際值爲 <null>', value=null, constraint='NotNullConstraint', expectValue='not null'}], allList=null}

方法初步說明

ValidBs 用來進行驗證的引導類,上述的寫法等價於如下:

public void helloValidAllConfigTest() {
    IResult result = ValidBs.on(null, JsrConstraints.notNullConstraint())
            .fail(Fails.failFast())
            .group()
            .valid(DefaultValidator.getInstance())
            .result()
            .print();
    Assert.assertFalse(result.pass());
}

on(Object value, IConstraint... constraints) 指定約束

Object 可以是對象,也可以是普通的值。

constraints 爲對應的約束列表,爲默認的約束驗證提供便利性。

IConstraint 相關創建工具類 ConstraintsJsrConstraints

fail(IFail fail)

可以指定失敗時的處理策略,支持用戶自定義失敗策略。

實現 說明
failOver 失敗後繼續驗證,直到驗證完所有屬性
failFast 失敗後快速返回

group(Class[] classes) 支持分組驗證

有時候我們希望,只驗證指定某一分組的約束。

可以通過 group() 屬性指定,與 IConstraint 中的 group() 屬性匹配的約束纔會被執行。

valid(IValidator validator) 支持驗證策略

默認爲 DefaultValidator,爲 valid-api 的實現驗證。

如果你希望使用 jsr-303 註解,可以使用 JsrValidator

支持自定義驗證策略。

result(IResultHandler resultHandler) 驗證結果處理

默認爲 simple() 的簡單結果處理。

可以指定爲 detail() 進行詳細結果處理查看。

支持用戶自定義結果處理策略。

IResult 內置方法

simple()/detail() 處理的結果爲 IResult 實現類。

IResult 支持如下方法:

  • print()

對結果進行打印,主要便於調試。

  • throwEx()

對於參數的校驗,一般都是基於異常結合 spring aop來處理的。

throwsEx 會在驗證不通過時,拋出 ValidRuntimeException 異常,對應 message 爲提示消息。

@Test(expected = ValidRuntimeException.class)
public void resultThrowsExTest() {
    ValidBs.on(null, notNullValidatorEntry())
            .valid()
            .result()
            .throwsEx();
}

內置的屬性約束

上面我們對 ValidBs 有了一個整體的瞭解,下面來看一看系統內置的屬性約束有哪些。

每個屬性約束都有對應註解。

針對單個屬性,直接使用屬性約束即可,靈活快捷。

針對 bean 校驗,可以結合註解實現,類似於 hibernate-validator。

valid-core

核心內置屬性約束實現。

enumRangesConstraint

枚舉類指定範圍約束

  • 創建方式

參見工具類 Constraints#enumRangesConstraint

/**
 * 枚舉範圍內約束
 * (1)當前值必須在枚舉類對應枚舉的 toString() 列表中。
 * @param enumClass 枚舉類,不可爲空
 * @return 約束類
 * @since 0.1.1
 * @see com.github.houbb.valid.core.annotation.constraint.EnumRanges 枚舉類指定範圍註解
 */
public static IConstraint enumRangesConstraint(final Class<? extends Enum> enumClass)
  • 測試案例

參見測試類 EnumsRangesConstraintTest

IResult result = ValidBs.on("DEFINE", Constraints.enumRangesConstraint(FailTypeEnum.class))
    .result();

Assert.assertFalse(result.pass());
  • 說明

FailTypeEnum 是 valid-api 內置的枚舉類,枚舉值爲 FAIL_FAST/FAIL_OVER。

只有屬性值在枚舉值範圍內,驗證纔會通過。

rangesConstraint

指定屬性範圍內約束

  • 創建方式

參見工具類 Constraints#rangesConstraint


 * 值在指定範圍內約束
 * (1)這裏爲了和註解保持一致性,暫時只支持 String
 * @param strings 對象範圍
 * @return 約束類
 * @since 0.1.1
 * @see com.github.houbb.valid.core.annotation.constraint.Ranges String 指定範圍內註解
 */
public static IConstraint rangesConstraint(String ... strings)
  • 測試案例

參見測試類 RangesConstraintTest

IResult result = ValidBs.on("DEFINE", Constraints.rangesConstraint("FAIL_OVER",
        "FAIL_FAST"))
    .result();

Assert.assertFalse(result.pass());
  • 說明

這個相對於枚舉值,更加靈活一些。

可以根據自己的需要,指定屬性的範圍。

valid-jsr

valid-jsr 中內置註解,和 jsr-303 標準一一對應,此處不再贅述。

創建方式見工具類 JsrConstraints,測試代碼見 xxxConstraintTest。

對應列表如下:

屬性約束 註解 簡介
AssertFalseConstraint @AssertFalse 指定值必須爲 false
AssertTrueConstraint @AssertTrue 指定值必須爲 true
MinConstraint @Min 指定值必須大於等於最小值
MaxConstraint @Max 指定值必須小於等於最大值
DecimalMinConstraint @DecimalMin 指定金額必須大於等於最小值
DecimalMaxConstraint @DecimalMax 指定金額必須小於等於最大值
DigitsConstraint @Digits 指定值位數必須符合要求
FutureConstraint @Future 指定日期必須在未來
PastConstraint @Past 指定日期必須在過去
PatternConstraint @Pattern 指定值必須滿足正則表達式
SizeConstraint @Size 指定值必須在指定大小內

自定義約束實現

需求

實際業務需求的是不斷變化的,內置的屬性約束常常無法滿足我們的實際需求。

我們可以通過自定義屬性,來實現自己的需求。

例子

參見類 DefineConstraintTest

自定義 notNullConstraint

notNullConstraint 對於 null 值是嚴格的。

所以繼承自 AbstractStrictConstraint,如下:

IResult result = ValidBs.on(null, new AbstractStrictConstraint() {
    @Override
    protected boolean pass(IConstraintContext context, Object value) {
        return value != null;
    }
}).result();

Assert.assertFalse(result.pass());

自定義 assertTrueConstraint

在 jsr-303 標準中,除卻 @NotNull 對於 null 值都是非嚴格校驗的。

繼承自 AbstractConstraint 即可,如下:

IConstraint assertTrueConstraint = new AbstractConstraint<Boolean>() {
    @Override
    protected boolean pass(IConstraintContext context, Boolean value) {
        return false;
    }
};

IResult nullValid = ValidBs.on(null, assertTrueConstraint)
        .result();
Assert.assertTrue(nullValid.pass());

IResult falseValid = ValidBs.on(false, assertTrueConstraint)
        .result();
Assert.assertFalse(falseValid.pass());

core 模塊註解驗證

內置註解

註解 說明
@AllEquals 當前字段及指定字段值必須全部相等
@HasNotNull 當前字段及指定字段值至少有一個不爲 null
@EnumRanges 當前字段值必須在枚舉屬性範圍內
@Ranges 當前字段值必須在指定屬性範圍內

測試對象

  • User.java
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;

    //fluent getter & setter
}

我們限制 name/nickName 至少有一個不爲空,password/password2 值要一致。

以及限定了 sex 的範圍值和 failType 的枚舉值。

測試代碼

User user = new User();
user.sex("what").password("old").password2("new")
    .failType("DEFINE");

IResult result = ValidBs.on(user)
        .fail(Fails.failOver())
        .result()
        .print();

Assert.assertFalse(result.pass());
  • 日誌
DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='值 <null> 不是預期值', value=null, constraint='HasNotNullConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <old> 不是預期值', value=old, constraint='AllEqualsConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <what> 不是預期值', value=what, constraint='RangesConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <DEFINE> 不是預期值', value=DEFINE, constraint='EnumRangesConstraint', expectValue=''}], allList=null}

jsr 模塊註解驗證

註解

與 jsr-303 註解標準保持一致。

對象定義

爲了演示,簡單定義如下:

  • JsrUser.java
public class JsrUser {

    @Null
    private Object nullVal;

    @NotNull
    private String notNullVal;

    @AssertFalse
    private boolean assertFalse;

    @AssertTrue
    private boolean assertTrue;

    @Pattern(regexp = "[123456]{2}")
    private String pattern;

    @Size(min = 2, max = 5)
    private String size;

    @DecimalMax("12.22")
    private BigDecimal decimalMax;

    @DecimalMin("1.22")
    private BigDecimal decimalMin;

    @Min(10)
    private long min;

    @Max(10)
    private long max;

    @Past
    private Date past;

    @Future
    private Date future;

    @Digits(integer = 2, fraction = 4)
    private Long digits;

    //fluent getter and setter
}

測試代碼

參見測試類 ValidBsJsrBeanTest

public void beanFailTest() {
    Date future = DateUtil.getFormatDate("90190101", DateUtil.PURE_DATE_FORMAT);
    Date past = DateUtil.getFormatDate("20190101", DateUtil.PURE_DATE_FORMAT);

    JsrUser jsrUser = new JsrUser();
    jsrUser.assertFalse(true)
            .assertTrue(false)
            .decimalMin(new BigDecimal("1"))
            .decimalMax(new BigDecimal("55.55"))
            .min(5)
            .max(20)
            .digits(333333L)
            .future(past)
            .past(future)
            .nullVal("123")
            .notNullVal(null)
            .pattern("asdfasdf")
            .size("22222222222222222222");

    IResult result = ValidBs.on(jsrUser)
            .fail(Fails.failOver())
            .valid(JsrValidator.getInstance())
            .result()
            .print();

    Assert.assertFalse(result.pass());
}
  • 日誌
DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='值必須爲空', value=123, constraint='NullConstraint', expectValue='null'}, DefaultConstraintResult{pass=false, message='值必須爲非空', value=null, constraint='NotNullConstraint', expectValue='not null'}, DefaultConstraintResult{pass=false, message='值必須爲假', value=true, constraint='AssertFalseConstraint', expectValue='false'}, DefaultConstraintResult{pass=false, message='值必須爲真', value=false, constraint='AssertTrueConstraint', expectValue='true'}, DefaultConstraintResult{pass=false, message='值必須滿足正則表達式', value=asdfasdf, constraint='PatternConstraint', expectValue='必須匹配正則表達式 [123456]{2}'}, DefaultConstraintResult{pass=false, message='值必須爲在指定範圍內', value=22222222222222222222, constraint='SizeConstraint', expectValue='大小必須在範圍內 [2, 5]'}, DefaultConstraintResult{pass=false, message='值必須小於金額最大值', value=55.55, constraint='DecimalMaxConstraint', expectValue='小於等於 12.22'}, DefaultConstraintResult{pass=false, message='值必須大於金額最小值', value=1, constraint='DecimalMinConstraint', expectValue='大於等於 1.22'}, DefaultConstraintResult{pass=false, message='值必須大於最小值', value=5, constraint='MinConstraint', expectValue='大於等於 10'}, DefaultConstraintResult{pass=false, message='值必須小於最大值', value=20, constraint='MaxConstraint', expectValue='小於等於 10'}, DefaultConstraintResult{pass=false, message='時間必須在過去', value=Fri Jan 01 00:00:00 CST 9019, constraint='PastConstraint', expectValue='小於等於 Sun Oct 13 12:12:07 CST 2019'}, DefaultConstraintResult{pass=false, message='時間必須在未來', value=Tue Jan 01 00:00:00 CST 2019, constraint='FutureConstraint', expectValue='大於等於 Sun Oct 13 12:12:07 CST 2019'}, DefaultConstraintResult{pass=false, message='值必須滿足位數', value=333333, constraint='DigitsConstraint', expectValue='整數位數 [2], 小數位數 [4]'}], allList=null}

@Valid 遞歸屬性驗證

需求

有時候我們一個對象中,會引入其他子對象。

我們希望對子對象也進行相關屬性的驗證,這時候就可以使用 @Valid 註解。

該註解爲 jsr-303 標準註解。

對象定義

public class ValidUser {

    /**
     * 子節點
     */
    @Valid
    private User user;

    //fluent setter & getter

}

測試代碼

參見測試類 ValidBsValidBeanTest

public void beanFailTest() {
    User user = new User();
    user.sex("default").password("old").password2("new")
            .failType("DEFINE");

    ValidUser validUser = new ValidUser();
    validUser.user(user);

    IResult result = ValidBs.on(validUser)
            .fail(Fails.failOver())
            .result()
            .print();

    Assert.assertFalse(result.pass());
}
  • 日誌信息
DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='值 <null> 不是預期值', value=null, constraint='HasNotNullConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <old> 不是預期值', value=old, constraint='AllEqualsConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <default> 不是預期值', value=default, constraint='RangesConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <DEFINE> 不是預期值', value=DEFINE, constraint='EnumRangesConstraint', expectValue=''}], allList=null}

自引用問題

有時候我們可能會引用自身,這個也做了測試,是符合預期的。

參見 ValidBsSelfValidBeanTest

i18n 支持

需求

不同國家對於語言的要求肯定也不同。

本項目目前支持中文/英文國際化支持,默認以當前地區編碼爲準,如果不存在,則使用英文。

感覺其他語言,暫時使用中沒有用到。(個人也不會,錯了也不知道。暫時不添加)

指定爲英文

測試代碼參加 ValidBsI18NTest

public void i18nEnTest() {
    Locale.setDefault(Locale.ENGLISH);
    IResult result = ValidBs.on(null, JsrConstraints.notNullConstraint())
            .result()
            .print();

    Assert.assertEquals("Expect is <not null>, but actual is <null>.", result.notPassList().get(0).message());
}

指定爲中文

public void i18nZhTest() {
    Locale.setDefault(Locale.CHINESE);
    IResult result = ValidBs.on(null, JsrConstraints.notNullConstraint())
            .result()
            .print();

    Assert.assertEquals("預期值爲 <not null>,實際值爲 <null>", result.notPassList().get(0).message());
}

IFail 失敗策略接口詳解

需求

對於不符合約束條件的處理方式,主要有以下兩種:

  • failFast

快速失敗。遇到一個約束不符合條件,直接返回。

優點:耗時較短。

  • failOver

全部驗證,將所有的屬性都驗證一遍。

優點:可以一次性獲得所有失敗信息。

創建方式

參見工具類 Fails,返回的實例爲單例,且線程安全。

測試代碼

參見測試類 ValidBsFailTest

failFast

我們指定要求屬性值長度最小爲3,且必須滿足正則表達式。

IResult result = ValidBs.on("12", JsrConstraints.sizeConstraintMin(3),
        JsrConstraints.patternConstraint("[678]{3}"))
        .fail(Fails.failFast())
        .result()
        .print();

Assert.assertEquals(1, result.notPassList().size());
  • 日誌

採用快速失敗模式,只有一個失敗驗證結果。

DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='預期值爲 <必須匹配正則表達式 [678]{3}>,實際值爲 <12>', value=12, constraint='PatternConstraint', expectValue='必須匹配正則表達式 [678]{3}'}], allList=null}

failOver

保持其他部分不變,我們調整下失敗處理策略。

IResult result = ValidBs.on("12", JsrConstraints.sizeConstraintMin(3),
        JsrConstraints.patternConstraint("[678]{3}"))
        .fail(Fails.failOver())
        .result()
        .print();

Assert.assertEquals(2, result.notPassList().size());
  • 日誌

此時失敗處理結果爲2,日誌如下:

DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='預期值爲 <必須匹配正則表達式 [678]{3}>,實際值爲 <12>', value=12, constraint='PatternConstraint', expectValue='必須匹配正則表達式 [678]{3}'}, DefaultConstraintResult{pass=false, message='預期值爲 <大小必須在範圍內 [3, 2147483647]>,實際值爲 <2>', value=12, constraint='SizeConstraint', expectValue='大小必須在範圍內 [3, 2147483647]'}], allList=null}

IValidator 驗證策略接口詳解

需求

爲了便於集成不同框架的測試驗證,本框架支持 IValidator。

同時也允許用戶自定義自己的實現方式。

默認驗證器策略-DefaultValidator

指定 valid 對應的驗證器,通過 ValidBs.valid(IValidator) 方法指定。

默認爲 DefaultValidator。

該驗證策略,支持符合 valid-api 的內置註解,及用戶自定義註解。

JSR-303 驗證器策略-JsrValidator

JsrValidator 支持 jsr-303 標準註解,及 valid-api 標準的相關注解實現和約束實現。

  • 使用方式

通過 valid 方法指定即可。

IResult result = ValidBs.on(jsrUser)
                .valid(JsrValidator.getInstance())
                .result()
                .print();

自定義驗證器策略

如果你想添加自己的實現,直接實現 IValidator,並且在 valid() 中指定即可。

可以參考 DefaultValidator,建議繼承自 AbstractValidator

IResultHandler 結果處理策略接口詳解

需求

對於驗證的結果,不同的場景,需求也各不相同。

你可能有如下需求:

(1)輸出驗證失敗的信息

(2)輸出所有驗證信息

(3)針對驗證失敗的信息拋出異常

(4)對驗證結果進行自定義處理。

爲了滿足上述需求,提供瞭如下的接口,及內置默認實現。

接口

public interface IResultHandler<T> {

    /**
     * 對約束結果進行統一處理
     * @param constraintResultList 約束結果列表
     * @return 結果
     */
    T handle(final List<IConstraintResult> constraintResultList);

}

如果你想自定義處理方式,實現此接口。

並在 ValidBs.result(IResultHandler) 方法中指定使用即可。

簡單實現

  • 說明

僅僅對沒有通過測試的驗證結果進行保留。

  • 測試代碼

參見測試代碼 ValidBsResultHandlerTest

ValidBs.on("12", JsrConstraints.sizeConstraintMin(2))
        .result(ResultHandlers.simple())
        .print();
  • 日誌
DefaultResult{pass=true, notPassList=[], allList=null}

詳細實現

  • 說明

保留所有驗證結果信息,包含通過驗證測試的明細信息。

  • 測試代碼

參見測試代碼 ValidBsResultHandlerTest

ValidBs.on("12", JsrConstraints.sizeConstraintMin(2))
        .result(ResultHandlers.detail())
        .print();
  • 測試日誌
DefaultResult{pass=true, notPassList=[], allList=[DefaultConstraintResult{pass=true, message='null', value=12, constraint='SizeConstraint', expectValue='null'}]}

IResult 結果接口詳解

說明

IResult 爲驗證結果處理的內置實現接口。

擁有以下常見方法:

方法 說明
pass() 是否通過驗證
notPassList() 未通過驗證的列表
allList() 所有驗證的列表
print() 控臺輸出驗證結果
throwsEx() 針對未通過驗證的信息拋出 ValidRuntimeException

測試代碼

@Test(expected = ValidRuntimeException.class)
public void methodsTest() {
    IResult result = ValidBs.on("12", JsrConstraints.sizeConstraintMin(3))
            .result(ResultHandlers.detail())
            .print()
            .throwsEx();

    Assert.assertFalse(result.pass());
    Assert.assertEquals(1, result.notPassList().size());
    Assert.assertEquals(1, result.allList().size());
}
  • 日誌
DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='預期值爲 <大小必須在範圍內 [3, 2147483647]>,實際值爲 <2>', value=12, constraint='SizeConstraint', expectValue='大小必須在範圍內 [3, 2147483647]'}], allList=[DefaultConstraintResult{pass=false, message='預期值爲 <大小必須在範圍內 [3, 2147483647]>,實際值爲 <2>', value=12, constraint='SizeConstraint', expectValue='大小必須在範圍內 [3, 2147483647]'}]}

IConstraint 約束接口詳解

需求

Hibernate-validator 主要是基於註解的 Bean 驗證,所以將註解和實現耦合在了一起。

Valid 作爲一個 fluent-api 驗證框架,支持過程式編程,所以將針對屬性驗證的約束獨立出來,便於複用。

接口說明

public interface IConstraint {

    /**
     * 觸發約束規則
     * @param context 上下文
     * @return 結果
     * @since 0.0.3
     */
    IConstraintResult constraint(final IConstraintContext context);

}

自定義說明

前面的例子已經演示瞭如何自定義實現。

直接實現上述接口也可以,建議繼承 AbstractConstraint 等內置的各種約束抽象類。

IValidEntry 驗證明細接口詳解

說明

當我們將 IConstraint 獨立出來時,同時有下面的一些問題:

(1)如何指定對應 message

(2)如何指定約束生效條件 condition

(3)如何指定約束的分組信息 group

IValidEntry 接口就是爲了解決這些問題,在 IConstraint 的基礎之上進行一系列的功能增強。

使用方式

測試代碼,參見類 ValidBsValidEntryTest

IValidEntry validEntry = ValidEntry.of(JsrConstraints.notNullConstraint());

IResult result = ValidBs.on(null, validEntry)
    .result()
    .print();

Assert.assertFalse(result.pass());

message() 自定義提示消息

我們可以自定義改約束條件的提示消息。

final IValidEntry validEntry = ValidEntry.of(JsrConstraints.notNullConstraint())
        .message("自定義:指定值不能爲空");

IResult result = ValidBs.on(null, validEntry)
        .valid()
        .result();

Assert.assertEquals("自定義:指定值不能爲空", result.notPassList().get(0).message());

group() 分組驗證

需求

有時候我們希望只驗證某一種分組的約束條件。

測試代碼

按照如下方式制定,只有當 ValidEntry 的 group 信息與 ValidBs.group() 符合時,纔會被執行。

final IValidEntry firstEntry = ValidEntry.of(JsrConstraints.sizeConstraint(5, 10))
        .group(String.class);

final IValidEntry otherEntry = ValidEntry.of(JsrConstraints.sizeConstraint(3, 20))
        .group(Integer.class);

IResult result = ValidBs
        .on("12", firstEntry, otherEntry)
        .fail(Fails.failOver())
        .group(String.class)
        .result();

Assert.assertEquals(1, result.notPassList().size());

condition 拓展

其實可以 group() 只是 condition 的一個特例。

後續將實現 ICondition 接口的相關內置支持,和 @Condition 註解的相關支持。

自定義註解

需求

說到 hibernate-validator,個人覺得最靈魂的設計就是支持用戶自定義註解了。

註解使得使用便利,自定義註解同時保證了靈活性。

下面來看看,如何實現自定義註解。

核心設計理念

你可以認爲內置註解也是一種自定義註解。

本框架的所有實現理念都是如此,可以認爲所有的內置實現,都是可以被替換的。

@AllEquals 註解解析

我們以 @AllEquals 註解爲例,

註解內容

@Inherited
@Documented
@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 {};

}

其中 group()/message() 和 IValidEntry 中的方法一一對應。

當然你設計的註解中如果沒有這兩個方法也沒關係,建議提供這兩個屬性。

註解與約束的關係

@Constraint(AtAllEqualsConstraint.class) 這個註解指定了當前註解與對應的約束實現,是最核心的部分。

@Constraint 註解

@Inherited
@Documented
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraint {

    /**
     * 約束條件實現類
     * @return 實現類 class
     */
    Class<? extends IAnnotationConstraint> value();

}

IAnnotationConstraint 接口

這個就是註解相關的約束接口,內容如下:

/**
 * 註解約束規則接口
 * 注意:所有的實現類都需要提供無參構造函數。
 * @author binbin.hou
 * @since 0.0.9
 */
public interface IAnnotationConstraint<A extends Annotation> extends IConstraint {

    /**
     * 初始化映射關係
     * @param annotation 註解信息
     * @since 0.0.9
     */
    void initialize(A annotation);

}

後期特性

  • 豐富 IConstraintResult 特性

  • 優化 IResult 使用體驗

  • @Condition 註解支持和 ICondition 的支持。

  • 集成 hibernate-validator 校驗

參考項目

JSR 標準

JSR 380

JSR 303

bean validation 2.0

bean 驗證框架

hibernate validate

apache bval

Fluent 框架

fluent-validator

FluentValidation

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章