1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        請(qǐng)不要再使用判斷進(jìn)行參數(shù)校驗(yàn)了

        共 3425字,需瀏覽 7分鐘

         ·

        2020-07-30 10:11

        1. 前言

        因?yàn)榫W(wǎng)絡(luò)傳輸?shù)牟豢煽啃?,以及前端?shù)據(jù)控制的可篡改性,后端的參數(shù)校驗(yàn)是必須的,應(yīng)用程序必須通過(guò)某種手段來(lái)確保輸入進(jìn)來(lái)的數(shù)據(jù)從語(yǔ)義上來(lái)講是正確的。


        2. 數(shù)據(jù)校驗(yàn)的痛點(diǎn)

        為了保證數(shù)據(jù)語(yǔ)義的正確,我們需要進(jìn)行大量的判斷來(lái)處理驗(yàn)證邏輯。而且項(xiàng)目的分層也會(huì)造成一些重復(fù)的校驗(yàn),產(chǎn)生大量與業(yè)務(wù)無(wú)關(guān)的代碼。不利于代碼的維護(hù),增加了開發(fā)人員的工作量。


        3. JSR 303 校驗(yàn)規(guī)范及其實(shí)現(xiàn)

        為了解決上面的痛點(diǎn),將驗(yàn)證邏輯與相應(yīng)的領(lǐng)域模型進(jìn)行綁定是十分有必要的。為此產(chǎn)生了JSR 303 – Bean Validation 規(guī)范。Hibernate Validator 是JSR-303的參考實(shí)現(xiàn),它提供了JSR 303規(guī)范中所有的約束(constraint)的實(shí)現(xiàn),同時(shí)也增加了一些擴(kuò)展。

        Hibernate Validator 提供的常用約束注解

        約束注解詳細(xì)信息
        @Null被注釋的元素必須為 null
        @NotNull被注釋的元素必須不為 null
        @AssertTrue被注釋的元素必須為 true
        @AssertFalse被注釋的元素必須為 false
        @Min(value)被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值
        @Max(value)被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值
        @DecimalMin(value)被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值
        @DecimalMax(value)被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值
        @Size(max, min)被注釋的元素的大小必須在指定的范圍內(nèi)
        @Digits (integer, fraction)被注釋的元素必須是一個(gè)數(shù)字,其值必須在可接受的范圍內(nèi)
        @Past被注釋的元素必須是一個(gè)過(guò)去的日期
        @Future被注釋的元素必須是一個(gè)將來(lái)的日期
        @Pattern(value)被注釋的元素必須符合指定的正則表達(dá)式
        @Email被注釋的元素必須是電子郵箱地址
        @Length被注釋的字符串的大小必須在指定的范圍內(nèi)
        @NotEmpty被注釋的字符串的必須非空
        @Range被注釋的元素必須在合適的范圍內(nèi)


        4. 驗(yàn)證注解的使用

        Spring Boot開發(fā)中使用Hibernate Validator是非常容易的,引入下面的starter就可以了:

        <dependency>
        ????<groupId>org.springframework.bootgroupId>
        ????<artifactId>spring-boot-starter-validationartifactId>
        dependency>

        一種可以實(shí)現(xiàn)接口來(lái)定制Validator,一種是使用約束注解。胖哥覺得注解可以滿足絕大部分的需求,所以建議使用注解來(lái)進(jìn)行數(shù)據(jù)校驗(yàn)。而且注解更加靈活,控制的粒度也更加細(xì)。接下來(lái)我們來(lái)學(xué)習(xí)如何使用注解進(jìn)行數(shù)據(jù)校驗(yàn)。

        4.1 約束注解的基本使用

        我們對(duì)需要校驗(yàn)的方法入?yún)⑦M(jìn)行注解約束標(biāo)記,例子如下:

        @Data
        public?class?Student?{

        ????@NotBlank(message?=?"姓名必須填")
        ????private?String?name;
        ????@NotNull(message?=?"年齡必須填寫")
        ????@Range(min?=?1,max?=50,?message?=?"年齡取值范圍1-50")
        ????private?Integer?age;
        ????@NotEmpty(message?=?"成績(jī)必填")
        ????private?List?scores;
        }

        POST 請(qǐng)求

        然后定義一個(gè)POST請(qǐng)求的Spring MVC接口:

        @RestController
        @RequestMapping("/student")
        public?class?StudentController?{


        ????@PostMapping("/add")
        ????public?Rest?addStudent(@Valid?@RequestBody?Student?student)?{
        ????????return?RestBody.okData(student);
        ????}
        }

        通過(guò)對(duì)addStudent方法入?yún)⑻砑?/span>@Valid來(lái)啟用參數(shù)校驗(yàn)。當(dāng)使用下面數(shù)據(jù)進(jìn)行請(qǐng)求將會(huì)拋出MethodArgumentNotValidException異常,提示age范圍超出1-50。

        POST /student/add HTTP/1.1
        Host: localhost:8888
        Content-Type: application/json

        {
        "name": "felord.cn",
        "age": 77,
        "scores": [
        55
        ]
        }

        GET 請(qǐng)求

        如法炮制,我們定義一個(gè)GET請(qǐng)求的接口:

        @GetMapping("/get")
        public?Rest?getStudent(@Valid?Student?student)?{
        ????return?RestBody.okData(student);
        }

        使用下面的請(qǐng)求可以正確對(duì)學(xué)生分?jǐn)?shù)scores進(jìn)行了校驗(yàn),但是拋出的并不是MethodArgumentNotValidException異常,而是BindException異常。這和使用@RequestBody注解有關(guān)系,這對(duì)我們后面的統(tǒng)一處理非常十分重要。

        GET /student/get?name=felord.cn&age=12 HTTP/1.1
        Host: localhost:8888

        自定義注解

        可能有些同學(xué)注意到上面的年齡我進(jìn)行了這樣的標(biāo)記:

        @NotNull(message?=?"年齡必須填寫")
        @Range(min?=?1,max?=50,?message?=?"年齡取值范圍1-50")
        private?Integer?age;

        這是因?yàn)?/span>@Range不會(huì)去校驗(yàn)為空的情況,它只處理非空的時(shí)候是否符合范圍約束。所以要用多個(gè)注解來(lái)約束。如果我們某些場(chǎng)景需要重復(fù)的捆綁多個(gè)注解來(lái)使用時(shí),可以使用自定義注解將它們封裝起來(lái)組合使用,下面這個(gè)注解就是將@NotNull@Range進(jìn)行了組合,你可以仿一個(gè)出來(lái)用用看。

        import?org.hibernate.validator.constraints.Range;

        import?javax.validation.Constraint;
        import?javax.validation.Payload;
        import?javax.validation.ReportAsSingleViolation;
        import?javax.validation.constraints.NotNull;
        import?javax.validation.constraintvalidation.SupportedValidationTarget;
        import?javax.validation.constraintvalidation.ValidationTarget;
        import?java.lang.annotation.*;

        /**
        ?*?@author?a
        ?*?@since?17:31
        ?**/

        @Constraint(
        ????????validatedBy?=?{}
        )
        @SupportedValidationTarget({ValidationTarget.ANNOTATED_ELEMENT})
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.METHOD,?ElementType.FIELD,
        ????????ElementType.ANNOTATION_TYPE,?ElementType.CONSTRUCTOR,
        ????????ElementType.PARAMETER,?ElementType.TYPE_USE})
        @NotNull
        @Range(min?=?1,?max?=?50)
        @Documented
        @ReportAsSingleViolation
        public?@interface?Age?{
        ????//?message?必須有
        ????String?message()?default?"年齡必須填寫,且范圍為?1-50?";

        ????//?可選
        ????Class[]?groups()?default?{};

        ????//?可選
        ????Class[]?payload()?default?{};
        }

        還有一種情況,我們?cè)诤笈_(tái)定義了枚舉值來(lái)進(jìn)行狀態(tài)的流轉(zhuǎn),也是需要校驗(yàn)的,比如我們定義了顏色枚舉:

        public?enum?Colors?{

        ????RED,?YELLOW,?BLUE

        }

        我們希望入?yún)⒉荒艹?/span>Colors的范圍["RED", "YELLOW", "BLUE"],這就需要實(shí)現(xiàn)ConstraintValidator接口來(lái)定義一個(gè)顏色約束了,其中泛型A為自定義的約束注解,泛型T為入?yún)⒌念愋?,這里使用字符串,然后我們的實(shí)現(xiàn)如下:

        /**
        ?*?@author?felord.cn
        ?*?@since?17:57
        ?**/

        public?class?ColorConstraintValidator?implements?ConstraintValidator<Color,?String>?{
        ????private?static?final?Set?COLOR_CONSTRAINTS?=?new?HashSet<>();

        ????@Override
        ????public?void?initialize(Color?constraintAnnotation)?{
        ????????Colors[]?value?=?constraintAnnotation.value();
        ????????List?list?=?Arrays.stream(value)
        ????????????????.map(Enum::name)
        ????????????????.collect(Collectors.toList());
        ????????COLOR_CONSTRAINTS.addAll(list);

        ????}

        ????@Override
        ????public?boolean?isValid(String?value,?ConstraintValidatorContext?context)?{
        ????????return?COLOR_CONSTRAINTS.contains(value);
        ????}
        }
        然后聲明對(duì)應(yīng)的約束注解Color,需要在元注解@Constraint中指明使用上面定義好的處理類ColorConstraintValidator進(jìn)行校驗(yàn)。
        /**
        ?*?@author?felord.cn
        ?*?@since?17:55
        ?**/

        @Constraint(validatedBy?=?ColorConstraintValidator.class)
        @Documented
        @Target({ElementType.METHOD,?ElementType.FIELD,
        ????????ElementType.ANNOTATION_TYPE,?ElementType.CONSTRUCTOR,
        ????????ElementType.PARAMETER,?ElementType.TYPE_USE})
        @Retention(RetentionPolicy.RUNTIME)
        public?@interface?Color?{
        ????//?錯(cuò)誤提示信息
        ????String?message()?default?"顏色不符合規(guī)格";

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

        ????Class[]?payload()?default?{};

        ????//?約束的類型
        ????Colors[]?value();
        }

        然后我們來(lái)試一下,先對(duì)參數(shù)進(jìn)行約束:

        @Data
        public?class?Param?{
        ????@Color({Colors.BLUE,Colors.YELLOW})
        ???private?String?color;
        }

        接口跟上面幾個(gè)一樣,調(diào)用下面的接口將拋出BindException異常:

        GET /student/color?color=CAY HTTP/1.1
        Host: localhost:8888

        當(dāng)我們把參數(shù)color賦值為BLUE或者YELLOW后,能夠成功得到響應(yīng)。

        4.2 常見問題

        在實(shí)際使用起來(lái)我們會(huì)遇到一些問題,這里總結(jié)了一些常見的問題和處理方式。

        檢驗(yàn)基礎(chǔ)類型不生效的問題

        上面為了校驗(yàn)顏色我們聲明了一個(gè)Param對(duì)象來(lái)包裝唯一的字符串參數(shù)color,為什么直接使用下面的方式定義呢?

        @GetMapping("/color")
        public?Rest?color(@Valid?@Color({Colors.BLUE,Colors.YELLOW})?String?color)?{
        ????return?RestBody.okData(color);
        }

        或者使用路徑變量:

        @GetMapping("/rest/{color}")
        public?Rest?rest(@Valid?@Color({Colors.BLUE,?Colors.YELLOW})?@PathVariable?String?color)?{
        ????return?RestBody.okData(color);
        }

        上面兩種方式是不會(huì)生效的。不信你可以試一試,起碼在Spring Boot 2.3.1.RELEASE是不會(huì)直接生效的。

        使以上兩種生效的方法是在類上添加@Validated注解。注意一定要添加到方法所在的類上才行。這時(shí)候會(huì)拋出ConstraintViolationException異常。

        集合類型參數(shù)中的元素不生效的問題

        就像下面的寫法,方法的參數(shù)為集合時(shí),如何檢驗(yàn)元素的約束呢?

        /**
        ?*?集合類型參數(shù)元素.
        ?*
        ?*?@param?student?the?student
        ?*?@return?the?rest
        ?*/

        @PostMapping("/batchadd")
        public?Rest?batchAddStudent(@Valid?@RequestBody?List?student)?{
        ????return?RestBody.okData(student);
        }

        同樣是在類上添加@Validated注解。注意一定要添加到方法所在的類上才行。這時(shí)候會(huì)拋出ConstraintViolationException異常。

        嵌套校驗(yàn)不生效

        嵌套的結(jié)構(gòu)如何校驗(yàn)?zāi)兀看騻€(gè)比方,如果我們?cè)趯W(xué)生類Student中添加了其所屬的學(xué)校信息School并希望對(duì)School的屬性進(jìn)行校驗(yàn)。

        @Data
        public?class?Student?{

        ????@NotBlank(message?=?"姓名必須填")
        ????private?String?name;
        ????@Age
        ????private?Integer?age;
        ????@NotEmpty(message?=?"成績(jī)必填")
        ????private?List?scores;
        ????@NotNull(message?=?"學(xué)校不能為空")
        ????private?School?school;
        }


        @Data
        public?class?School?{
        ????@NotBlank(message?=?"學(xué)校名稱不能為空")
        ????private?String?name;
        ????@Min(value?=?0,message?="校齡大于0"?)
        ????private?Integer?age;
        }

        當(dāng) GET請(qǐng)求時(shí)正常校驗(yàn)了School的屬性,但是POST請(qǐng)求卻無(wú)法對(duì)School的屬性進(jìn)行校驗(yàn)。這時(shí)我們只需要在該屬性上加上@Valid注解即可。

        @Data
        public?class?Student?{

        ????@NotBlank(message?=?"姓名必須填")
        ????private?String?name;
        ????@Age
        ????private?Integer?age;
        ????@NotEmpty(message?=?"成績(jī)必填")
        ????private?List?scores;
        ????@Valid
        ????@NotNull(message?=?"學(xué)校不能為空")
        ????private?School?school;
        }

        每加一層嵌套都需要加一層@Valid注解。通常在校驗(yàn)對(duì)象屬性時(shí),@NotNull、@NotEmpty@Valid配合才能起到校驗(yàn)效果。


        有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

        歡迎大家關(guān)注Java之道公眾號(hào)


        好文章,我在看??

        瀏覽 40
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            成人电影一二三区 | 欧美不卡视频在线 | 玖热精品 | 榴莲视频| 影音先锋色资源网 | 色播播电影 | 久久国产黄色视频 | 乌克兰一级黄色片 | 女生张开腿让男生捅视频 | 欧美猛少妇色xxxxx免费看 |