hibernate validation 提供了很多做數(shù)據(jù)校驗(yàn)用的注解類闯参,我相信同學(xué)們一定使用過@Valid 注解和 BindingResult 驗(yàn)證請(qǐng)求參數(shù)的合法性并處理校驗(yàn)結(jié)果亏镰。
update 方法
@PutMapping(value = ["/{id:\\d+}"])
fun update(@Valid @RequestBody user: User, errors: BindingResult): User {
if (errors.hasErrors()) {
errors.allErrors.forEach { error -> println(error.defaultMessage)
}
println(user.id)
user.id = "1"
return user
}
??@Valid 與 @NotBlank @Email @Pattern 要配合使用绕娘,這樣在傳入的參數(shù)發(fā)生錯(cuò)誤時(shí)才能在控制臺(tái)輸出錯(cuò)誤的信息芜抒。BindingResult 有著這樣一個(gè)作用苦酱,它能讓我們帶著異常進(jìn)入到update這個(gè)方法中礼华,并通過判斷 if (errors.hasErrors()) { } 將非法的參數(shù)記錄下來伞鲫。
??雖然hibernate validator 有提供很多甚至像@Pattern這樣可以用正則表達(dá)式去自定義注解库物,但是很多情況下hibernate提供的這些注解霸旗,并不能滿足我們的需求 只是非常簡(jiǎn)單的邏輯最復(fù)雜的只是讓你寫個(gè)正則表達(dá)式如圖中的@Pattern注解類, 但是很多時(shí)候我們校驗(yàn)是要基于數(shù)據(jù)的比如說這個(gè)用戶 這個(gè)訂單是不是存在它在數(shù)據(jù)庫是不是重復(fù)的等等 這些校驗(yàn)他不是簡(jiǎn)單的判斷一下你傳上來的值就可以了艳狐,它還需要其他的校驗(yàn)邏輯定硝,這是我們必須要自己去寫一些校驗(yàn)的邏輯,那么自己去寫這些校驗(yàn)邏輯就需要自己寫注解了毫目。
?? 在這之前我們先創(chuàng)建一個(gè) MyConstraintValidator.kt 這個(gè)類蔬啡,它就是我們的校驗(yàn)邏輯類,并且讓它繼承 ConstraintValidator<MyConstraint, Any> 注意這個(gè) Any 在 java 中就是 object 這個(gè)類镀虐,kotlin中有一個(gè)長(zhǎng)得很像的一個(gè)類叫 Objects,注意就是多了個(gè)s箱蟆。默認(rèn)繼承的時(shí)候一般選中String,今天從java代碼中照搬的時(shí)候不小心就寫成了Objects,這個(gè)如果錯(cuò)寫成Objects 控制臺(tái)會(huì)報(bào)一個(gè) No validator could be found for constraint 并拋出一個(gè)UnexpectedTypeException 異常。但如果你寫成Object 它是不會(huì)拋出異常的而且順利執(zhí)行刮便,但是idea會(huì)有警告空猜。就因?yàn)槎喑鰜淼倪@個(gè)s,花了我一下午的時(shí)間去排錯(cuò)恨旱。但也是因?yàn)檫@個(gè)s辈毯,我才發(fā)現(xiàn)我泛型寫錯(cuò)了。
MyConstraintValidator.kt 類
package com.fara.security.demo.validator
import com.fara.security.demo.service.HelloService
import org.springframework.beans.factory.annotation.Autowired
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
/**
* Created by 黃德輝 on 2018/04/11 15:56
**/
class MyConstraintValidator : ConstraintValidator<MyConstraint, Any> {
@Autowired
private lateinit var helloService: HelloService
/**
* 初始化時(shí)調(diào)用
*/
override fun initialize(constraintAnnotation: MyConstraint?) {
super.initialize(constraintAnnotation)
println("my validator init 初始化方法")
}
override fun isValid(value: Any?, context: ConstraintValidatorContext?): Boolean {
helloService.greeting("huangdehui") //此處沒有在控制臺(tái)輸出日志
println(value)
//返回 false 會(huì)顯示出 message = "這是一個(gè)測(cè)試方法的值“
return false
}
}
寫完這個(gè)類以后搜贤,我們添加這個(gè)類里面未實(shí)現(xiàn)的方法谆沃,isValid,再重寫一個(gè) initialize仪芒。它一共有兩個(gè)方法唁影,一個(gè)是初始化耕陷,一個(gè)是校驗(yàn)器,那么iniitialize這個(gè)方法我們簡(jiǎn)單的打一句話就可以了 println("my validator init 初始化方法")据沈, 這個(gè)就是初始化要完成的事情哟沫,另一個(gè)就是校驗(yàn)邏輯 isValid 方法 一共有兩個(gè)參數(shù),一個(gè)是你校驗(yàn)時(shí)傳進(jìn)來的值 value 然后另一個(gè)是你的上下文 context 這里面真正包含了你注解類的一些值锌介,我們直接吧這個(gè)值打出來嗜诀,要注意的是這里面你可以使用 spring 里的注解,比如 @Autowired 我舉個(gè)例子創(chuàng)建一個(gè) helloService 掏湾,注意這個(gè)類不需要在上面加 @Component spring 看到 MyConstraintValidator 實(shí)現(xiàn)了ConstraintValidator這個(gè)接口后裹虫,它會(huì)自動(dòng)的把它做為springBean。
那么我們接下去在User類中為username字段添加的@MyConstraint注解類融击,就是通過@Constraint(validatedBy = [MyConstraintValidator::class])注解的方式指定了約束的這個(gè)類筑公。那么當(dāng)我們這個(gè) Controller update這個(gè)方法它在校驗(yàn)User的時(shí)候,它發(fā)現(xiàn)User上面寫的 @MyConstraint(message = "這是一個(gè)測(cè)試") 時(shí)候 它就會(huì)調(diào)用 MyConstraintValidator 里面的尊浪,isValid方法匣屡,然后它會(huì)把請(qǐng)求體中 json 格式對(duì)應(yīng)username的值,當(dāng)做參數(shù)傳進(jìn)來然后我會(huì)根據(jù)這個(gè)值去校驗(yàn)拇涤,返回true 或者 false 捣作,返回失敗的時(shí)候它就會(huì)把 @MyConstraint(message="這是一個(gè)測(cè)試"),注解中的massage存放到我們最終的 BindingResult errors 中的這個(gè)對(duì)象里去最后通過
if (errors.hasErrors()) {
errors.allErrors.forEach { error -> println(error.defaultMessage)
}
這種方式循環(huán)出來 打到控制臺(tái)上鹅士,通過這個(gè)循環(huán)你還可以將信息打印到對(duì)應(yīng)的日志上券躁。
那么先來觀察一下這個(gè)User類
User類
/**
* Created by 黃德輝 on 2018/04/11 04:38
**/
class User {
interface UserSimpleView
//User詳細(xì)視圖 要繼承 User簡(jiǎn)單視圖 在顯示詳細(xì)視圖的時(shí)候會(huì)把簡(jiǎn)單視圖里全部顯示出來
interface UserDetailView : UserSimpleView
@JsonView(UserSimpleView::class)
var id: String? = ""
@MyConstraint(message = "這是一個(gè)測(cè)試")
@JsonView(UserSimpleView::class)
var username: String? = ""
@NotBlank(message = "密碼不能為空")
@JsonView(UserDetailView::class)
var password: String? = ""
/**
* 日期在前后分離的情況下最好存時(shí)間戳,格式的顯示交給前端來處理
* 有些場(chǎng)景需要 網(wǎng)頁上的時(shí)間格式與手機(jī)上的不一樣.不要去傳帶格式的時(shí)間
* 前端要展現(xiàn)成什么讓前臺(tái)來決定 后臺(tái)就傳時(shí)間就可以了
*/
@Past(message = "生日必須是過去時(shí)間") //必須是一個(gè)過去的時(shí)間 因?yàn)槿说纳湛隙ㄊ且粋€(gè)過去的時(shí)間,不可能是未來的時(shí)間
@JsonView(UserSimpleView::class)
var birthday: Date? = Date()
@Pattern(regexp = "[1-7]{1}", message = "reason的類型值為1-7中的一個(gè)類型")
val reson:String? = null
}
這里有一個(gè)注解類叫@MyConstraint(message = "這是一個(gè)測(cè)試")掉盅,這就是我們要實(shí)現(xiàn)的注解那么該怎么寫呢也拜?我們先按住 Ctrl 這個(gè)鍵然后點(diǎn)擊鼠標(biāo)右鍵查看@NotBlank 注解類,參考一下這個(gè)java注解類趾痘。
@NotBlank 注解類
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NotBlank {
String message() default "{javax.validation.constraints.NotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {@code @NotBlank} constraints on the same element.
*
* @see NotBlank
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
}
在這里有 validation 注解類需要的非常重要的三個(gè)成員變量分別是
- String message() default "{javax.validation.constraints.NotBlank.message}";
- Class<?>[] groups() default { };
- Class<? extends Payload>[] payload() default { };
任何你自己寫的 validator 注解慢哈,都必須包含 message groups payload 這三個(gè)成員變量。
message 想必大家都知道了永票, groups 與 payload 都是 hibernate validator 里的概念卵贱,關(guān)系不大就不深入的去介紹,有興趣的同學(xué)可以自己去看一下相關(guān)的文檔侣集。
那么這里我們依葫蘆畫瓢創(chuàng)建一個(gè)注解類 MyConstraint.kt
MyConstraint.kt 注解類
package com.fara.security.demo.validator
import javax.validation.Constraint
import javax.validation.Payload
import kotlin.reflect.KClass
/**
* Created by 黃德輝 on 2018/04/11 20:18
**/
@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = [MyConstraintValidator::class])
annotation class MyConstraint(
val message: String,
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
可以看到的是kotlin的語法真的很簡(jiǎn)單哈键俱,我們照搬 @NotBlank 這個(gè)注解后,這樣就有了一個(gè)注解世分,以及它相應(yīng)的校驗(yàn)器了编振,這時(shí)候就可以將這個(gè)注解類寫到任何一個(gè)類字段或方法上面了。
@MyConstraint(message = "這是一個(gè)測(cè)試")
@JsonView(UserSimpleView::class)
var username: String? = ""
當(dāng)調(diào)用到 Controller 中的這個(gè)update 方法的時(shí)候
@PutMapping(value = ["/{id:\\d+}"])
fun update(@Valid @RequestBody user: User, errors: BindingResult): User {
if (errors.hasErrors()) {
errors.allErrors.forEach { error -> println(error.defaultMessage)
}
println(user.id)
user.id = "1"
return user
}
它就會(huì)自動(dòng)去校驗(yàn) User 罚攀,并將傳入username的字段值党觅,以參數(shù)的形式傳入到MyConstraintValidator.kt中的isValid方法中進(jìn)行校驗(yàn),方法返回 false 的時(shí)候它既能拋出異常也可以記錄用戶是否傳入了什么樣的非法值斋泄。
其實(shí)這個(gè)@MyConstraint自定義注解類最花費(fèi)我時(shí)間的地方就是就是將message groups payload 這三個(gè)成員變量這三個(gè)成員變量如何用 kotlin 來表達(dá)杯瞻,對(duì)于我這個(gè)剛?cè)腴Tkotlin第二天的新手而言我翻爛了官方文檔有關(guān)于這三個(gè)成員變量的書寫,由于在 Kotlin 中 沒有成員變量聲明是用 [ ] 這種方式來實(shí)現(xiàn)的炫掐,還有對(duì)java注解類的不了解給我?guī)砹朔浅6嗟睦Щ笈c麻煩魁莉。如果對(duì) java 注解類不熟悉的同學(xué)們可以參考我這篇文章 java 注解類 。
該學(xué)習(xí)筆記的源代碼 https://gitee.com/huangdehui/fara-security-demo 來源于spring security demo 根據(jù)慕課網(wǎng)上spring security學(xué)習(xí)書寫的筆記募胃。