溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java bean 屬性驗(yàn)證框架 valid

發(fā)布時(shí)間:2020-07-01 21:41:01 來(lái)源:網(wǎng)絡(luò) 閱讀:139644 作者:葉止水ryo 欄目:編程語(yǔ)言

項(xiàng)目介紹

java 開(kāi)發(fā)中,參數(shù)校驗(yàn)是非常常見(jiàn)的需求。

但是 hibernate-validator 在使用過(guò)程中,依然會(huì)存在一些問(wèn)題。

特性

  • 支持 fluent-validation

  • 支持 jsr-303 注解

  • 支持 i18n

  • 支持用戶自定義策略

  • 支持用戶自定義注解

開(kāi)源地址

valid

創(chuàng)作目的

hibernate-validator 無(wú)法滿足的場(chǎng)景

如今 java 最流行的 hibernate-validator 框架,但是有些場(chǎng)景是無(wú)法滿足的。

比如:

  1. 驗(yàn)證新密碼和確認(rèn)密碼是否相同。(同一對(duì)象下的不同屬性之間關(guān)系)

  2. 當(dāng)一個(gè)屬性值滿足某個(gè)條件時(shí),才進(jìn)行其他值的參數(shù)校驗(yàn)。

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

其實(shí),在對(duì)于多個(gè)字段的關(guān)聯(lián)關(guān)系處理時(shí),hibernate-validator 就會(huì)比較弱。

本項(xiàng)目結(jié)合原有的優(yōu)點(diǎn),進(jìn)行這一點(diǎn)的功能強(qiáng)化。

validation-api 過(guò)于復(fù)雜

validation-api 提供了豐富的特性定義,也同時(shí)帶來(lái)了一個(gè)問(wèn)題。

實(shí)現(xiàn)起來(lái),特別復(fù)雜。

然而我們實(shí)際使用中,常常不需要這么復(fù)雜的實(shí)現(xiàn)。

valid-api 提供了一套簡(jiǎn)化很多的 api,便于用戶自行實(shí)現(xiàn)。

自定義缺乏靈活性

hibernate-validator 在使用中,自定義約束實(shí)現(xiàn)是基于注解的,針對(duì)單個(gè)屬性校驗(yàn)不夠靈活。

本項(xiàng)目中,將屬性校驗(yàn)約束和注解約束區(qū)分開(kāi),便于復(fù)用和拓展。

過(guò)程式編程 vs 注解式編程

hibernate-validator 核心支持的是注解式編程,基于 bean 的校驗(yàn)。

一個(gè)問(wèn)題是針對(duì)屬性校驗(yàn)不靈活,有時(shí)候針對(duì) bean 的校驗(yàn),還是要自己寫判斷。

本項(xiàng)目支持 fluent-api 進(jìn)行過(guò)程式編程,同時(shí)支持注解式編程。

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

項(xiàng)目模塊說(shuō)明

模塊名稱 說(shuō)明
valid-api 核心 api 及注解定義
valid-core 針對(duì) valid-api 的核心實(shí)現(xiàn)
valid-jsr 針對(duì) JSR-303 標(biāo)準(zhǔn)注解的實(shí)現(xiàn)
valid-test 測(cè)試代碼模塊

依賴說(shuō)明

valid-core 默認(rèn)引入 valid-api

valid-jsr 默認(rèn)引入 valid-core

快速開(kāi)始

準(zhǔn)備工作

JDK1.7+

Maven 3.X+

maven 引入

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

例子

我們直接利用 jsr 內(nèi)置的約束類:

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

對(duì)應(yīng)日志輸出為:

DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='預(yù)期值為 <not null>,實(shí)際值為 <null>', value=null, constraint='NotNullConstraint', expectValue='not null'}], allList=null}

方法初步說(shuō)明

ValidBs 用來(lái)進(jìn)行驗(yàn)證的引導(dǎo)類,上述的寫法等價(jià)于如下:

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 可以是對(duì)象,也可以是普通的值。

constraints 為對(duì)應(yīng)的約束列表,為默認(rèn)的約束驗(yàn)證提供便利性。

IConstraint 相關(guān)創(chuàng)建工具類 ConstraintsJsrConstraints

fail(IFail fail)

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

實(shí)現(xiàn) 說(shuō)明
failOver 失敗后繼續(xù)驗(yàn)證,直到驗(yàn)證完所有屬性
failFast 失敗后快速返回

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

有時(shí)候我們希望,只驗(yàn)證指定某一分組的約束。

可以通過(guò) group() 屬性指定,與 IConstraint 中的 group() 屬性匹配的約束才會(huì)被執(zhí)行。

valid(IValidator validator) 支持驗(yàn)證策略

默認(rèn)為 DefaultValidator,為 valid-api 的實(shí)現(xiàn)驗(yàn)證。

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

支持自定義驗(yàn)證策略。

result(IResultHandler resultHandler) 驗(yàn)證結(jié)果處理

默認(rèn)為 simple() 的簡(jiǎn)單結(jié)果處理。

可以指定為 detail() 進(jìn)行詳細(xì)結(jié)果處理查看。

支持用戶自定義結(jié)果處理策略。

IResult 內(nèi)置方法

simple()/detail() 處理的結(jié)果為 IResult 實(shí)現(xiàn)類。

IResult 支持如下方法:

  • print()

對(duì)結(jié)果進(jìn)行打印,主要便于調(diào)試。

  • throwEx()

對(duì)于參數(shù)的校驗(yàn),一般都是基于異常結(jié)合 spring aop來(lái)處理的。

throwsEx 會(huì)在驗(yàn)證不通過(guò)時(shí),拋出 ValidRuntimeException 異常,對(duì)應(yīng) message 為提示消息。

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

內(nèi)置的屬性約束

上面我們對(duì) ValidBs 有了一個(gè)整體的了解,下面來(lái)看一看系統(tǒng)內(nèi)置的屬性約束有哪些。

每個(gè)屬性約束都有對(duì)應(yīng)注解。

針對(duì)單個(gè)屬性,直接使用屬性約束即可,靈活快捷。

針對(duì) bean 校驗(yàn),可以結(jié)合注解實(shí)現(xiàn),類似于 hibernate-validator。

valid-core

核心內(nèi)置屬性約束實(shí)現(xiàn)。

enumRangesConstraint

枚舉類指定范圍約束

  • 創(chuàng)建方式

參見(jiàn)工具類 Constraints#enumRangesConstraint

/**
 * 枚舉范圍內(nèi)約束
 * (1)當(dāng)前值必須在枚舉類對(duì)應(yīng)枚舉的 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)
  • 測(cè)試案例

參見(jiàn)測(cè)試類 EnumsRangesConstraintTest

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

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

FailTypeEnum 是 valid-api 內(nèi)置的枚舉類,枚舉值為 FAIL_FAST/FAIL_OVER。

只有屬性值在枚舉值范圍內(nèi),驗(yàn)證才會(huì)通過(guò)。

rangesConstraint

指定屬性范圍內(nèi)約束

  • 創(chuàng)建方式

參見(jiàn)工具類 Constraints#rangesConstraint


 * 值在指定范圍內(nèi)約束
 * (1)這里為了和注解保持一致性,暫時(shí)只支持 String
 * @param strings 對(duì)象范圍
 * @return 約束類
 * @since 0.1.1
 * @see com.github.houbb.valid.core.annotation.constraint.Ranges String 指定范圍內(nèi)注解
 */
public static IConstraint rangesConstraint(String ... strings)
  • 測(cè)試案例

參見(jiàn)測(cè)試類 RangesConstraintTest

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

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

這個(gè)相對(duì)于枚舉值,更加靈活一些。

可以根據(jù)自己的需要,指定屬性的范圍。

valid-jsr

valid-jsr 中內(nèi)置注解,和 jsr-303 標(biāo)準(zhǔn)一一對(duì)應(yīng),此處不再贅述。

創(chuàng)建方式見(jiàn)工具類 JsrConstraints,測(cè)試代碼見(jiàn) xxxConstraintTest。

對(duì)應(yīng)列表如下:

屬性約束 注解 簡(jiǎn)介
AssertFalseConstraint @AssertFalse 指定值必須為 false
AssertTrueConstraint @AssertTrue 指定值必須為 true
MinConstraint @Min 指定值必須大于等于最小值
MaxConstraint @Max 指定值必須小于等于最大值
DecimalMinConstraint @DecimalMin 指定金額必須大于等于最小值
DecimalMaxConstraint @DecimalMax 指定金額必須小于等于最大值
DigitsConstraint @Digits 指定值位數(shù)必須符合要求
FutureConstraint @Future 指定日期必須在未來(lái)
PastConstraint @Past 指定日期必須在過(guò)去
PatternConstraint @Pattern 指定值必須滿足正則表達(dá)式
SizeConstraint @Size 指定值必須在指定大小內(nèi)

自定義約束實(shí)現(xiàn)

需求

實(shí)際業(yè)務(wù)需求的是不斷變化的,內(nèi)置的屬性約束常常無(wú)法滿足我們的實(shí)際需求。

我們可以通過(guò)自定義屬性,來(lái)實(shí)現(xiàn)自己的需求。

例子

參見(jiàn)類 DefineConstraintTest

自定義 notNullConstraint

notNullConstraint 對(duì)于 null 值是嚴(yán)格的。

所以繼承自 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 標(biāo)準(zhǔn)中,除卻 @NotNull 對(duì)于 null 值都是非嚴(yán)格校驗(yàn)的。

繼承自 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 模塊注解驗(yàn)證

內(nèi)置注解

注解 說(shuō)明
@AllEquals 當(dāng)前字段及指定字段值必須全部相等
@HasNotNull 當(dāng)前字段及指定字段值至少有一個(gè)不為 null
@EnumRanges 當(dāng)前字段值必須在枚舉屬性范圍內(nèi)
@Ranges 當(dāng)前字段值必須在指定屬性范圍內(nèi)

測(cè)試對(duì)象

  • 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 至少有一個(gè)不為空,password/password2 值要一致。

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

測(cè)試代碼

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> 不是預(yù)期值', value=null, constraint='HasNotNullConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <old> 不是預(yù)期值', value=old, constraint='AllEqualsConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <what> 不是預(yù)期值', value=what, constraint='RangesConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <DEFINE> 不是預(yù)期值', value=DEFINE, constraint='EnumRangesConstraint', expectValue=''}], allList=null}

jsr 模塊注解驗(yàn)證

注解

與 jsr-303 注解標(biāo)準(zhǔn)保持一致。

對(duì)象定義

為了演示,簡(jiǎn)單定義如下:

  • 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
}

測(cè)試代碼

參見(jiàn)測(cè)試類 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='值必須滿足正則表達(dá)式', value=asdfasdf, constraint='PatternConstraint', expectValue='必須匹配正則表達(dá)式 [123456]{2}'}, DefaultConstraintResult{pass=false, message='值必須為在指定范圍內(nèi)', value=22222222222222222222, constraint='SizeConstraint', expectValue='大小必須在范圍內(nèi) [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='時(shí)間必須在過(guò)去', value=Fri Jan 01 00:00:00 CST 9019, constraint='PastConstraint', expectValue='小于等于 Sun Oct 13 12:12:07 CST 2019'}, DefaultConstraintResult{pass=false, message='時(shí)間必須在未來(lái)', value=Tue Jan 01 00:00:00 CST 2019, constraint='FutureConstraint', expectValue='大于等于 Sun Oct 13 12:12:07 CST 2019'}, DefaultConstraintResult{pass=false, message='值必須滿足位數(shù)', value=333333, constraint='DigitsConstraint', expectValue='整數(shù)位數(shù) [2], 小數(shù)位數(shù) [4]'}], allList=null}

@Valid 遞歸屬性驗(yàn)證

需求

有時(shí)候我們一個(gè)對(duì)象中,會(huì)引入其他子對(duì)象。

我們希望對(duì)子對(duì)象也進(jìn)行相關(guān)屬性的驗(yàn)證,這時(shí)候就可以使用 @Valid 注解。

該注解為 jsr-303 標(biāo)準(zhǔn)注解。

對(duì)象定義

public class ValidUser {

    /**
     * 子節(jié)點(diǎn)
     */
    @Valid
    private User user;

    //fluent setter & getter

}

測(cè)試代碼

參見(jiàn)測(cè)試類 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> 不是預(yù)期值', value=null, constraint='HasNotNullConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <old> 不是預(yù)期值', value=old, constraint='AllEqualsConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <default> 不是預(yù)期值', value=default, constraint='RangesConstraint', expectValue=''}, DefaultConstraintResult{pass=false, message='值 <DEFINE> 不是預(yù)期值', value=DEFINE, constraint='EnumRangesConstraint', expectValue=''}], allList=null}

自引用問(wèn)題

有時(shí)候我們可能會(huì)引用自身,這個(gè)也做了測(cè)試,是符合預(yù)期的。

參見(jiàn) ValidBsSelfValidBeanTest

i18n 支持

需求

不同國(guó)家對(duì)于語(yǔ)言的要求肯定也不同。

本項(xiàng)目目前支持中文/英文國(guó)際化支持,默認(rèn)以當(dāng)前地區(qū)編碼為準(zhǔn),如果不存在,則使用英文。

感覺(jué)其他語(yǔ)言,暫時(shí)使用中沒(méi)有用到。(個(gè)人也不會(huì),錯(cuò)了也不知道。暫時(shí)不添加)

指定為英文

測(cè)試代碼參加 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("預(yù)期值為 <not null>,實(shí)際值為 <null>", result.notPassList().get(0).message());
}

IFail 失敗策略接口詳解

需求

對(duì)于不符合約束條件的處理方式,主要有以下兩種:

  • failFast

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

優(yōu)點(diǎn):耗時(shí)較短。

  • failOver

全部驗(yàn)證,將所有的屬性都驗(yàn)證一遍。

優(yōu)點(diǎn):可以一次性獲得所有失敗信息。

創(chuàng)建方式

參見(jiàn)工具類 Fails,返回的實(shí)例為單例,且線程安全。

測(cè)試代碼

參見(jiàn)測(cè)試類 ValidBsFailTest

failFast

我們指定要求屬性值長(zhǎng)度最小為3,且必須滿足正則表達(dá)式。

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

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

采用快速失敗模式,只有一個(gè)失敗驗(yàn)證結(jié)果。

DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='預(yù)期值為 <必須匹配正則表達(dá)式 [678]{3}>,實(shí)際值為 <12>', value=12, constraint='PatternConstraint', expectValue='必須匹配正則表達(dá)式 [678]{3}'}], allList=null}

failOver

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

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

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

此時(shí)失敗處理結(jié)果為2,日志如下:

DefaultResult{pass=false, notPassList=[DefaultConstraintResult{pass=false, message='預(yù)期值為 <必須匹配正則表達(dá)式 [678]{3}>,實(shí)際值為 <12>', value=12, constraint='PatternConstraint', expectValue='必須匹配正則表達(dá)式 [678]{3}'}, DefaultConstraintResult{pass=false, message='預(yù)期值為 <大小必須在范圍內(nèi) [3, 2147483647]>,實(shí)際值為 <2>', value=12, constraint='SizeConstraint', expectValue='大小必須在范圍內(nèi) [3, 2147483647]'}], allList=null}

IValidator 驗(yàn)證策略接口詳解

需求

為了便于集成不同框架的測(cè)試驗(yàn)證,本框架支持 IValidator。

同時(shí)也允許用戶自定義自己的實(shí)現(xiàn)方式。

默認(rèn)驗(yàn)證器策略-DefaultValidator

指定 valid 對(duì)應(yīng)的驗(yàn)證器,通過(guò) ValidBs.valid(IValidator) 方法指定。

默認(rèn)為 DefaultValidator。

該驗(yàn)證策略,支持符合 valid-api 的內(nèi)置注解,及用戶自定義注解。

JSR-303 驗(yàn)證器策略-JsrValidator

JsrValidator 支持 jsr-303 標(biāo)準(zhǔn)注解,及 valid-api 標(biāo)準(zhǔn)的相關(guān)注解實(shí)現(xiàn)和約束實(shí)現(xiàn)。

  • 使用方式

通過(guò) valid 方法指定即可。

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

自定義驗(yàn)證器策略

如果你想添加自己的實(shí)現(xiàn),直接實(shí)現(xiàn) IValidator,并且在 valid() 中指定即可。

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

IResultHandler 結(jié)果處理策略接口詳解

需求

對(duì)于驗(yàn)證的結(jié)果,不同的場(chǎng)景,需求也各不相同。

你可能有如下需求:

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

(2)輸出所有驗(yàn)證信息

(3)針對(duì)驗(yàn)證失敗的信息拋出異常

(4)對(duì)驗(yàn)證結(jié)果進(jìn)行自定義處理。

為了滿足上述需求,提供了如下的接口,及內(nèi)置默認(rèn)實(shí)現(xiàn)。

接口

public interface IResultHandler<T> {

    /**
     * 對(duì)約束結(jié)果進(jìn)行統(tǒng)一處理
     * @param constraintResultList 約束結(jié)果列表
     * @return 結(jié)果
     */
    T handle(final List<IConstraintResult> constraintResultList);

}

如果你想自定義處理方式,實(shí)現(xiàn)此接口。

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

簡(jiǎn)單實(shí)現(xiàn)

  • 說(shuō)明

僅僅對(duì)沒(méi)有通過(guò)測(cè)試的驗(yàn)證結(jié)果進(jìn)行保留。

  • 測(cè)試代碼

參見(jiàn)測(cè)試代碼 ValidBsResultHandlerTest

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

詳細(xì)實(shí)現(xiàn)

  • 說(shuō)明

保留所有驗(yàn)證結(jié)果信息,包含通過(guò)驗(yàn)證測(cè)試的明細(xì)信息。

  • 測(cè)試代碼

參見(jiàn)測(cè)試代碼 ValidBsResultHandlerTest

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

IResult 結(jié)果接口詳解

說(shuō)明

IResult 為驗(yàn)證結(jié)果處理的內(nèi)置實(shí)現(xiàn)接口。

擁有以下常見(jiàn)方法:

方法 說(shuō)明
pass() 是否通過(guò)驗(yàn)證
notPassList() 未通過(guò)驗(yàn)證的列表
allList() 所有驗(yàn)證的列表
print() 控臺(tái)輸出驗(yàn)證結(jié)果
throwsEx() 針對(duì)未通過(guò)驗(yàn)證的信息拋出 ValidRuntimeException

測(cè)試代碼

@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='預(yù)期值為 <大小必須在范圍內(nèi) [3, 2147483647]>,實(shí)際值為 <2>', value=12, constraint='SizeConstraint', expectValue='大小必須在范圍內(nèi) [3, 2147483647]'}], allList=[DefaultConstraintResult{pass=false, message='預(yù)期值為 <大小必須在范圍內(nèi) [3, 2147483647]>,實(shí)際值為 <2>', value=12, constraint='SizeConstraint', expectValue='大小必須在范圍內(nèi) [3, 2147483647]'}]}

IConstraint 約束接口詳解

需求

Hibernate-validator 主要是基于注解的 Bean 驗(yàn)證,所以將注解和實(shí)現(xiàn)耦合在了一起。

Valid 作為一個(gè) fluent-api 驗(yàn)證框架,支持過(guò)程式編程,所以將針對(duì)屬性驗(yàn)證的約束獨(dú)立出來(lái),便于復(fù)用。

接口說(shuō)明

public interface IConstraint {

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

}

自定義說(shuō)明

前面的例子已經(jīng)演示了如何自定義實(shí)現(xiàn)。

直接實(shí)現(xiàn)上述接口也可以,建議繼承 AbstractConstraint 等內(nèi)置的各種約束抽象類。

IValidEntry 驗(yàn)證明細(xì)接口詳解

說(shuō)明

當(dāng)我們將 IConstraint 獨(dú)立出來(lái)時(shí),同時(shí)有下面的一些問(wèn)題:

(1)如何指定對(duì)應(yīng) message

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

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

IValidEntry 接口就是為了解決這些問(wèn)題,在 IConstraint 的基礎(chǔ)之上進(jìn)行一系列的功能增強(qiáng)。

使用方式

測(cè)試代碼,參見(jiàn)類 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() 分組驗(yàn)證

需求

有時(shí)候我們希望只驗(yàn)證某一種分組的約束條件。

測(cè)試代碼

按照如下方式制定,只有當(dāng) ValidEntry 的 group 信息與 ValidBs.group() 符合時(shí),才會(huì)被執(zhí)行。

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 拓展

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

后續(xù)將實(shí)現(xiàn) ICondition 接口的相關(guān)內(nèi)置支持,和 @Condition 注解的相關(guān)支持。

自定義注解

需求

說(shuō)到 hibernate-validator,個(gè)人覺(jué)得最靈魂的設(shè)計(jì)就是支持用戶自定義注解了。

注解使得使用便利,自定義注解同時(shí)保證了靈活性。

下面來(lái)看看,如何實(shí)現(xiàn)自定義注解。

核心設(shè)計(jì)理念

你可以認(rèn)為內(nèi)置注解也是一種自定義注解。

本框架的所有實(shí)現(xiàn)理念都是如此,可以認(rèn)為所有的內(nèi)置實(shí)現(xiàn),都是可以被替換的。

@AllEquals 注解解析

我們以 @AllEquals 注解為例,

注解內(nèi)容

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

    /**
     * 當(dāng)前字段及其指定的字段 全部相等
     * 1. 字段類型及其他字段相同
     * @return 指定的字段列表
     */
    String[] value();

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

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

}

其中 group()/message() 和 IValidEntry 中的方法一一對(duì)應(yīng)。

當(dāng)然你設(shè)計(jì)的注解中如果沒(méi)有這兩個(gè)方法也沒(méi)關(guān)系,建議提供這兩個(gè)屬性。

注解與約束的關(guān)系

@Constraint(AtAllEqualsConstraint.class) 這個(gè)注解指定了當(dāng)前注解與對(duì)應(yīng)的約束實(shí)現(xiàn),是最核心的部分。

@Constraint 注解

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

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

}

IAnnotationConstraint 接口

這個(gè)就是注解相關(guān)的約束接口,內(nèi)容如下:

/**
 * 注解約束規(guī)則接口
 * 注意:所有的實(shí)現(xiàn)類都需要提供無(wú)參構(gòu)造函數(shù)。
 * @author binbin.hou
 * @since 0.0.9
 */
public interface IAnnotationConstraint<A extends Annotation> extends IConstraint {

    /**
     * 初始化映射關(guān)系
     * @param annotation 注解信息
     * @since 0.0.9
     */
    void initialize(A annotation);

}

后期特性

  • 豐富 IConstraintResult 特性

  • 優(yōu)化 IResult 使用體驗(yàn)

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

  • 集成 hibernate-validator 校驗(yàn)

參考項(xiàng)目

JSR 標(biāo)準(zhǔn)

JSR 380

JSR 303

bean validation 2.0

bean 驗(yàn)證框架

hibernate validate

apache bval

Fluent 框架

fluent-validator

FluentValidation

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI