java 開發(fā)中,參數(shù)校驗(yàn)是非常常見的需求。
鼓樓網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站開發(fā)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)建站于2013年成立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站。
但是 hibernate-validator 在使用過(guò)程中,依然會(huì)存在一些問(wèn)題。
支持 fluent-validation
支持 jsr-303 注解
支持 i18n
支持用戶自定義策略
valid
如今 java 最流行的 hibernate-validator 框架,但是有些場(chǎng)景是無(wú)法滿足的。
比如:
驗(yàn)證新密碼和確認(rèn)密碼是否相同。(同一對(duì)象下的不同屬性之間關(guān)系)
當(dāng)一個(gè)屬性值滿足某個(gè)條件時(shí),才進(jìn)行其他值的參數(shù)校驗(yàn)。
其實(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 提供了豐富的特性定義,也同時(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ū)分開,便于復(fù)用和拓展。
hibernate-validator 核心支持的是注解式編程,基于 bean 的校驗(yàn)。
一個(gè)問(wèn)題是針對(duì)屬性校驗(yàn)不靈活,有時(shí)候針對(duì) bean 的校驗(yàn),還是要自己寫判斷。
本項(xiàng)目支持 fluent-api 進(jìn)行過(guò)程式編程,同時(shí)支持注解式編程。
盡可能兼顧靈活性與便利性。
模塊名稱 | 說(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è)試代碼模塊 |
valid-core 默認(rèn)引入 valid-api
valid-jsr 默認(rèn)引入 valid-core
JDK1.7+
Maven 3.X+
<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}
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());
}
Object 可以是對(duì)象,也可以是普通的值。
constraints 為對(duì)應(yīng)的約束列表,為默認(rèn)的約束驗(yàn)證提供便利性。
IConstraint 相關(guān)創(chuàng)建工具類 Constraints
、JsrConstraints
可以指定失敗時(shí)的處理策略,支持用戶自定義失敗策略。
實(shí)現(xiàn) | 說(shuō)明 |
---|---|
failOver | 失敗后繼續(xù)驗(yàn)證,直到驗(yàn)證完所有屬性 |
failFast | 失敗后快速返回 |
有時(shí)候我們希望,只驗(yàn)證指定某一分組的約束。
可以通過(guò) group() 屬性指定,與 IConstraint 中的 group() 屬性匹配的約束才會(huì)被執(zhí)行。
默認(rèn)為 DefaultValidator,為 valid-api 的實(shí)現(xiàn)驗(yàn)證。
如果你希望使用 jsr-303 注解,可以使用 JsrValidator
。
支持自定義驗(yàn)證策略。
默認(rèn)為 simple() 的簡(jiǎn)單結(jié)果處理。
可以指定為 detail() 進(jìn)行詳細(xì)結(jié)果處理查看。
支持用戶自定義結(jié)果處理策略。
simple()/detail() 處理的結(jié)果為 IResult 實(shí)現(xiàn)類。
IResult 支持如下方法:
對(duì)結(jié)果進(jìn)行打印,主要便于調(diào)試。
對(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();
}
上面我們對(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。
核心內(nèi)置屬性約束實(shí)現(xià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è)試類 EnumsRangesConstraintTest
IResult result = ValidBs.on("DEFINE", Constraints.enumRangesConstraint(FailTypeEnum.class))
.result();
Assert.assertFalse(result.pass());
FailTypeEnum 是 valid-api 內(nèi)置的枚舉類,枚舉值為 FAIL_FAST/FAIL_OVER。
只有屬性值在枚舉值范圍內(nèi),驗(yàn)證才會(huì)通過(guò)。
指定屬性范圍內(nèi)約束
參見工具類 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è)試類 RangesConstraintTest
IResult result = ValidBs.on("DEFINE", Constraints.rangesConstraint("FAIL_OVER",
"FAIL_FAST"))
.result();
Assert.assertFalse(result.pass());
這個(gè)相對(duì)于枚舉值,更加靈活一些。
可以根據(jù)自己的需要,指定屬性的范圍。
valid-jsr 中內(nèi)置注解,和 jsr-303 標(biāo)準(zhǔn)一一對(duì)應(yīng),此處不再贅述。
創(chuàng)建方式見工具類 JsrConstraints
,測(cè)試代碼見 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í)際業(yè)務(wù)需求的是不斷變化的,內(nèi)置的屬性約束常常無(wú)法滿足我們的實(shí)際需求。
我們可以通過(guò)自定義屬性,來(lái)實(shí)現(xiàn)自己的需求。
參見類 DefineConstraintTest
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());
在 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());
注解 | 說(shuō)明 |
---|---|
@AllEquals | 當(dāng)前字段及指定字段值必須全部相等 |
@HasNotNull | 當(dāng)前字段及指定字段值至少有一個(gè)不為 null |
@EnumRanges | 當(dāng)前字段值必須在枚舉屬性范圍內(nèi) |
@Ranges | 當(dāng)前字段值必須在指定屬性范圍內(nèi) |
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 的枚舉值。
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-303 注解標(biāo)準(zhǔn)保持一致。
為了演示,簡(jiǎn)單定義如下:
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è)試類 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}
有時(shí)候我們一個(gè)對(duì)象中,會(huì)引入其他子對(duì)象。
我們希望對(duì)子對(duì)象也進(jìn)行相關(guān)屬性的驗(yàn)證,這時(shí)候就可以使用 @Valid
注解。
該注解為 jsr-303 標(biāo)準(zhǔn)注解。
public class ValidUser {
/**
* 子節(jié)點(diǎn)
*/
@Valid
private User user;
//fluent setter & getter
}
參見測(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}
有時(shí)候我們可能會(huì)引用自身,這個(gè)也做了測(cè)試,是符合預(yù)期的。
參見 ValidBsSelfValidBeanTest
不同國(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());
}
對(duì)于不符合約束條件的處理方式,主要有以下兩種:
快速失敗。遇到一個(gè)約束不符合條件,直接返回。
優(yōu)點(diǎn):耗時(shí)較短。
全部驗(yàn)證,將所有的屬性都驗(yàn)證一遍。
優(yōu)點(diǎn):可以一次性獲得所有失敗信息。
參見工具類 Fails
,返回的實(shí)例為單例,且線程安全。
參見測(cè)試類 ValidBsFailTest
我們指定要求屬性值長(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}
保持其他部分不變,我們調(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}
為了便于集成不同框架的測(cè)試驗(yàn)證,本框架支持 IValidator。
同時(shí)也允許用戶自定義自己的實(shí)現(xiàn)方式。
指定 valid 對(duì)應(yīng)的驗(yàn)證器,通過(guò) ValidBs.valid(IValidator)
方法指定。
默認(rèn)為 DefaultValidator。
該驗(yàn)證策略,支持符合 valid-api 的內(nèi)置注解,及用戶自定義注解。
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();
如果你想添加自己的實(shí)現(xiàn),直接實(shí)現(xiàn) IValidator,并且在 valid() 中指定即可。
可以參考 DefaultValidator,建議繼承自 AbstractValidator
。
對(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)
方法中指定使用即可。
僅僅對(duì)沒(méi)有通過(guò)測(cè)試的驗(yàn)證結(jié)果進(jìn)行保留。
參見測(cè)試代碼 ValidBsResultHandlerTest
ValidBs.on("12", JsrConstraints.sizeConstraintMin(2))
.result(ResultHandlers.simple())
.print();
DefaultResult{pass=true, notPassList=[], allList=null}
保留所有驗(yàn)證結(jié)果信息,包含通過(guò)驗(yàn)證測(cè)試的明細(xì)信息。
參見測(cè)試代碼 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 為驗(yàn)證結(jié)果處理的內(nèi)置實(shí)現(xià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 |
@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]'}]}
Hibernate-validator 主要是基于注解的 Bean 驗(yàn)證,所以將注解和實(shí)現(xiàn)耦合在了一起。
Valid 作為一個(gè) fluent-api 驗(yàn)證框架,支持過(guò)程式編程,所以將針對(duì)屬性驗(yàn)證的約束獨(dú)立出來(lái),便于復(fù)用。
public interface IConstraint {
/**
* 觸發(fā)約束規(guī)則
* @param context 上下文
* @return 結(jié)果
* @since 0.0.3
*/
IConstraintResult constraint(final IConstraintContext context);
}
前面的例子已經(jīng)演示了如何自定義實(shí)現(xiàn)。
直接實(shí)現(xiàn)上述接口也可以,建議繼承 AbstractConstraint
等內(nèi)置的各種約束抽象類。
當(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è)試代碼,參見類 ValidBsValidEntryTest
IValidEntry validEntry = ValidEntry.of(JsrConstraints.notNullConstraint());
IResult result = ValidBs.on(null, validEntry)
.result()
.print();
Assert.assertFalse(result.pass());
我們可以自定義改約束條件的提示消息。
final IValidEntry validEntry = ValidEntry.of(JsrConstraints.notNullConstraint())
.message("自定義:指定值不能為空");
IResult result = ValidBs.on(null, validEntry)
.valid()
.result();
Assert.assertEquals("自定義:指定值不能為空", result.notPassList().get(0).message());
有時(shí)候我們希望只驗(yàn)證某一種分組的約束條件。
按照如下方式制定,只有當(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());
其實(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)自定義注解。
你可以認(rèn)為內(nèi)置注解也是一種自定義注解。
本框架的所有實(shí)現(xiàn)理念都是如此,可以認(rèn)為所有的內(nèi)置實(shí)現(xiàn),都是可以被替換的。
我們以 @AllEquals
注解為例,
@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è)屬性。
@Constraint(AtAllEqualsConstraint.class)
這個(gè)注解指定了當(dāng)前注解與對(duì)應(yīng)的約束實(shí)現(xiàn),是最核心的部分。
@Inherited
@Documented
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraint {
/**
* 約束條件實(shí)現(xiàn)類
* @return 實(shí)現(xiàn)類 class
*/
Class<? extends IAnnotationConstraint> value();
}
這個(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 的支持。
JSR 380
JSR 303
bean validation 2.0
hibernate validate
apache bval
fluent-validator
FluentValidation
分享文章:javabean屬性驗(yàn)證框架valid
瀏覽路徑:http://muchs.cn/article4/gdddie.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、面包屑導(dǎo)航、品牌網(wǎng)站建設(shè)、定制開發(fā)、網(wǎng)站改版、網(wǎng)站設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
移動(dòng)網(wǎng)站建設(shè)知識(shí)