kotlin validation 自定義校驗(yàn)注解

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í)書寫的筆記募胃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旗唁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子痹束,更是在濱河造成了極大的恐慌检疫,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祷嘶,死亡現(xiàn)場(chǎng)離奇詭異屎媳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)论巍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門烛谊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嘉汰,你說我怎么就攤上這事丹禀。” “怎么了鞋怀?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵双泪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我接箫,道長(zhǎng)攒读,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任辛友,我火速辦了婚禮薄扁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘废累。我一直安慰自己邓梅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布邑滨。 她就那樣靜靜地躺著日缨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掖看。 梳的紋絲不亂的頭發(fā)上匣距,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天面哥,我揣著相機(jī)與錄音,去河邊找鬼毅待。 笑死尚卫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尸红。 我是一名探鬼主播吱涉,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼外里!你這毒婦竟也來了怎爵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤盅蝗,失蹤者是張志新(化名)和其女友劉穎鳖链,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體风科,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撒轮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贼穆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片题山。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖故痊,靈堂內(nèi)的尸體忽然破棺而出顶瞳,到底是詐尸還是另有隱情,我是刑警寧澤愕秫,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布慨菱,位于F島的核電站,受9級(jí)特大地震影響戴甩,放射性物質(zhì)發(fā)生泄漏符喝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一甜孤、第九天 我趴在偏房一處隱蔽的房頂上張望协饲。 院中可真熱鬧,春花似錦缴川、人聲如沸茉稠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽而线。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膀篮,已是汗流浹背嘹狞。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留誓竿,地道東北人刁绒。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像烤黍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傻盟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 在寫程序的時(shí)候經(jīng)常需要進(jìn)行數(shù)據(jù)校驗(yàn)速蕊,比如服務(wù)端對(duì)http請(qǐng)求參數(shù)校驗(yàn),數(shù)據(jù)入庫時(shí)對(duì)字段長(zhǎng)度進(jìn)行校驗(yàn)娘赴,接口參數(shù)校驗(yàn)规哲,...
    dayspring閱讀 9,834評(píng)論 0 9
  • 《Spring Boot 2.0 極簡(jiǎn)教程》附錄 I : Spring 5.0 新特性 因?yàn)镾pring Boot...
    光劍書架上的書閱讀 3,204評(píng)論 0 13
  • SpringMVC介紹之Validation 對(duì)于任何一個(gè)應(yīng)用而言在客戶端做的數(shù)據(jù)有效性驗(yàn)證都不是安全有效的,這時(shí)...
    yongguang423閱讀 1,186評(píng)論 0 16
  • 前言 人生苦多诽表,快來 Kotlin 唉锌,快速學(xué)習(xí)Kotlin! 什么是Kotlin竿奏? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,209評(píng)論 9 118
  • 對(duì)于任何一個(gè)應(yīng)用而言在客戶端做的數(shù)據(jù)有效性驗(yàn)證都不是安全有效的袄简,這時(shí)候就要求我們?cè)陂_發(fā)的時(shí)候在服務(wù)端也對(duì)數(shù)據(jù)的有效...
    低至一折起閱讀 559評(píng)論 0 1