Jsr303參數(shù)校驗(yàn)框架使用块请、拓展、封裝

無論是常規(guī)web開發(fā)還是服務(wù)端接口開發(fā)拳缠,出于安全性和系統(tǒng)健壯性的考慮墩新,都免不了要對(duì)入?yún)⑦M(jìn)行一系列校驗(yàn)。


沒有用參數(shù)校驗(yàn)框架之前的畫風(fēng)是這樣的(不上代碼了脊凰,直接貼圖)
image.png

對(duì)于這種原始校驗(yàn)給人的第一印象就是臃腫、無趣狸涌。當(dāng)參數(shù)數(shù)量達(dá)到一個(gè)可觀的級(jí)別我們寫這種校驗(yàn)更能體會(huì)到其中的酸軟切省,尤其是可能還會(huì)有手機(jī)號(hào)、郵箱等等稍復(fù)雜的校驗(yàn)帕胆。 專注于\color{red}{核(C)心(R)功(U)能(D)}開發(fā)的我們?cè)趺茨鼙贿@種鎖事所牽絆朝捆,束縛了手腳?懒豹!

用了參數(shù)校驗(yàn)框架后的畫風(fēng)是這樣的
    @Test
    public void validate() {
        Dog dto = Dog.builder()
                .name("趙二狗")
                .age(12)
                .pnoneNum("123456789")
                .weight(51D)
                .build();
        //一行代價(jià)搞定
        ParamValidatorUtil.validate(dto);
    }
}


@Builder
class Dog {
    @NotNull(message = "姓名不能為空")
    private String name;
    @NotNull(message = "年齡不能為空")
    @Max(value = 99,message = "年齡不合法")
    private Integer age;
    @NotNull(message = "手機(jī)號(hào)不能為空")
    private String pnoneNum;
    @DecimalMax(value = "50",message = "體重超標(biāo)")
    private Double weight;
}
運(yùn)行結(jié)果

結(jié)果立竿見影芙盘。原本啰里啰嗦又臭又長(zhǎng)的代碼現(xiàn)在只需要一行代碼就可以搞定。前提是在需要校驗(yàn)的實(shí)體類上加上校驗(yàn)注解脸秽。

\color{red}{JSR-303} 是Java EE 6 中的一項(xiàng)子規(guī)范儒老,叫做BeanValidation,官方參考實(shí)現(xiàn)是hibernate Validator(與Hibernate ORM 沒有關(guān)系)记餐,JSR 303 用于對(duì)Java Bean 中的字段的值進(jìn)行驗(yàn)證驮樊。 JSR 303 – Bean Validation 規(guī)范

\color{red}{Bean Validation }為 JavaBean 驗(yàn)證定義了相應(yīng)的元數(shù)據(jù)模型和 API。缺省的元數(shù)據(jù)是 Java Annotations片酝,通過使用 XML 可以對(duì)原有的元數(shù)據(jù)信息進(jìn)行覆蓋和擴(kuò)展囚衔。在應(yīng)用程序中,通過使用 Bean Validation 或是你自己定義的 constraint雕沿,例如 @NotNull, @Max, @ZipCode练湿, 就可以確保數(shù)據(jù)模型(JavaBean)的正確性。constraint 可以附加到字段审轮,getter 方法肥哎,類或者接口上面。對(duì)于一些特定的需求断国,用戶可以很容易的開發(fā)定制化的 constraint贤姆。Bean Validation 是一個(gè)運(yùn)行時(shí)的數(shù)據(jù)驗(yàn)證框架,在驗(yàn)證之后驗(yàn)證的錯(cuò)誤信息會(huì)被馬上返回稳衬。

Hibernate Validator是 Bean Validation 的參考實(shí)現(xiàn) . Hibernate Validator 提供了 JSR 303 規(guī)范中所有內(nèi)置 constraint 的實(shí)現(xiàn)霞捡,除此之外還有一些附加的 constraint。如果想了解更多有關(guān) Hibernate Validator 的信息薄疚,請(qǐng)查看 Hibernate Validator

\color{red}{表1:Bean Validation 中內(nèi)置的 constraint}

注解 詳細(xì)信息
@Null \color{green}{被注釋的元素必須不為 null}
@NotNull \color{green}{被注釋的元素必須不為 null}
@AssertTrue \color{green}{被注釋的元素必須為 true }
@AssertFalse \color{green}{被注釋的元素必須為 false }
@Min(value) \color{green}{被注釋的元素必須是一個(gè)數(shù)字碧信,其值必須大于等于指定的最小值}
@Max(value) \color{green}{被注釋的元素必須是一個(gè)數(shù)字赊琳,其值必須小于等于指定的最大值}
@DecimalMin(value) \color{green}{被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值 }
@DecimalMax(value) \color{green}{被注釋的元素必須是一個(gè)數(shù)字砰碴,其值必須小于等于指定的最大值}
@Size(max, min) \color{green}{被注釋的元素的大小必須在指定的范圍內(nèi)}
@Digits (integer, fraction) \color{green}{被注釋的元素必須是一個(gè)數(shù)字躏筏,其值必須在可接受的范圍內(nèi) }
@Past \color{green}{被注釋的元素必須是一個(gè)過去的日期 }
@Future \color{green}{被注釋的元素必須是一個(gè)將來的日期 }
@Pattern(value) \color{green}{被注釋的元素必須符合指定的正則表達(dá)式}

\color{red}{表2: Hibernate Validator 附加的 constraint}

注解 詳細(xì)信息
@Email \color{green}{被注釋的元素必須是電子郵箱地址}
@Length \color{green}{被注釋的字符串的大小必須在指定的范圍內(nèi)}
@NotEmpty \color{green}{被注釋的字符串的必須非空}
@Range \color{green}{被注釋的元素必須在合適的范圍內(nèi)}

一個(gè) constraint 通常由 annotation 和相應(yīng)的 constraint validator(校驗(yàn)器) 組成,它們是一對(duì)多的關(guān)系呈枉。也就是說可以有多個(gè) constraint validator 對(duì)應(yīng)一個(gè) annotation趁尼。在運(yùn)行時(shí),Bean Validation 框架本身會(huì)根據(jù)被注釋元素的類型來選擇合適的 constraint validator 對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證猖辫。

有些時(shí)候酥泞,在用戶的應(yīng)用中需要一些更復(fù)雜的 constraint。\color{red}{換句話說啃憎,他們提供的注解不夠用咋整芝囤??辛萍?}Bean Validation 提供擴(kuò)展 constraint 的機(jī)制悯姊。可以通過兩種方法去實(shí)現(xiàn)贩毕,一種是組合現(xiàn)有的 constraint 來生成一個(gè)更復(fù)雜的 constraint悯许,另外一種是開發(fā)一個(gè)全新的 constraint。

  • \color{red}{組合}
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
@DecimalMax("9999")
@DecimalMin("0.0")
public @interface Price {
    String message() default "價(jià)格無效";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}

Price 注解 由兩個(gè)內(nèi)置的 constraint DecimalMax辉阶,DecimalMin組合而成岸晦。

  • \color{red}{擴(kuò)展}
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {OrderStatusValidator.class})
public @interface OrderStatus {

    String message() default "訂單狀態(tài)不合法";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}
//用于校驗(yàn)OrderStatus注解 的校驗(yàn)器
public class OrderStatusValidator implements ConstraintValidator<OrderStatus,Integer> {
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return EnumUtils.containsVal(OrderStatusEnum.values(),value);
    }

    @Override
    public void initialize(OrderStatus constraintAnnotation) {
        //可以在此做一些初始化工作  例如 從獲取注解中的某些值
    }
}
public enum OrderStatusEnum implements IEnum{
    CONFIRMING(1,"待確認(rèn)"),
    SUCCEEDED(2,"成功"),
    ALLOCATED(3,"已排車"),
    TOOK(4,"已取車"),
    RETURNED(5,"已還車"),
    SETTLED(6,"已結(jié)算"),
    NONE(10,"無車"),
    CANCELED(11,"已取消");

\color{red}{注意:}自定義校驗(yàn)注解必須指定 自定義校驗(yàn)器。

    @Test
    public void validate() {
        Dog dto = Dog.builder()
                .amount(250D)
                .orderStatus(22)
                .build();

        ParamValidatorUtil.validate(dto);
    }

}
@Builder
class Dog {
    @Price
    private Double amount;
    @OrderStatus
    private Integer orderStatus;
}
執(zhí)行結(jié)果

由此可見睛藻,用了這玩意兒,我們的開發(fā)變的簡(jiǎn)單高效更加放飛自我邢隧。當(dāng)內(nèi)置的注解無法滿足我們的實(shí)際要求時(shí)店印,我們可以對(duì)之靈活的加以組合或拓展實(shí)現(xiàn)功能,從而做一個(gè)更加優(yōu)秀的api調(diào)用工程師倒慧。


工具類封裝

public class ParamValidatorUtil {
    public static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    /**
     * 校驗(yàn)實(shí)體參數(shù),返回第一條錯(cuò)誤信息
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> String validateV2(T t) {
        if (t == null){
            return "參數(shù)不能為空按摘!";
        }
        Set<ConstraintViolation<T>> validationSet = validator.validate(t, Default.class);
        String message = null;
        if (validationSet != null && validationSet.size() > 0) {
            ConstraintViolation<T> violation = validationSet.iterator().next();
            message = violation.getMessage();
        }
        return message;
    }

    public static <T> void validate(T t) {
        String msg = validateV2(t);
        if (msg != null) {
            throw new BizException(msg);
        }
    }

spring提供的支持

這么好用的功能,萬能膠spring理所當(dāng)然會(huì)對(duì)此進(jìn)行支持纫谅。SpringMVC模塊中添加了自動(dòng)校驗(yàn)炫贤,并將校驗(yàn)信息封裝進(jìn)了特定的類中。

以SpringBoot為例付秕,只需要引入spring-boot-starter-web starter即可兰珍。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
@RestController
public class TestController {
    @RequestMapping("/test")
    public String test(@Validated Dog dog) {
         ……
        return "success";
    }
}

參數(shù)Dog前需要加上@Validated注解,表明需要spring對(duì)其進(jìn)行校驗(yàn)询吴,而校驗(yàn)的信息會(huì)存放BindingResult對(duì)象中掠河。在Controller中可以根據(jù)業(yè)務(wù)邏輯來決定具體的操作亮元,通過全局異常類,跳轉(zhuǎn)到錯(cuò)誤頁面唠摹。

//全局異常類
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultVO handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        BindingResult bindingResult = e.getBindingResult();
        //返回一條錯(cuò)誤信息
        String msg = bindingResult.getFieldErrors().iterator().next().getDefaultMessage();

        return ResultVOUtils.error(ResultEnum.PARAM_ERROR.getCode(), msg );
    }
}

就寫到這吧爆捞,搞了將近倆小時(shí),該吃飯了勾拉。煮甥。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末藕赞,一起剝皮案震驚了整個(gè)濱河市成肘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌找默,老刑警劉巖艇劫,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惩激,居然都是意外死亡店煞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門风钻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顷蟀,“玉大人,你說我怎么就攤上這事骡技∶觯” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵布朦,是天一觀的道長(zhǎng)囤萤。 經(jīng)常有香客問我,道長(zhǎng)是趴,這世上最難降的妖魔是什么涛舍? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮唆途,結(jié)果婚禮上富雅,老公的妹妹穿的比我還像新娘。我一直安慰自己肛搬,他們只是感情好没佑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著温赔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扣溺,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音锥余,去河邊找鬼。 笑死驱犹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的足画。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼医舆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蔬将?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤霞怀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后莉给,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毙石,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年颓遏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了徐矩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叁幢,死狀恐怖丧蘸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遥皂,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布刽漂,位于F島的核電站演训,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏贝咙。R本人自食惡果不足惜样悟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窟她,春花似錦陈症、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吊说,卻和暖如春论咏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颁井。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工厅贪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雅宾。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓养涮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親眉抬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贯吓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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