<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          請(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ù),增加了開(kāi)發(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開(kāi)發(fā)中使用Hibernate Validator是非常容易的,引入下面的starter就可以了:

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

          一種可以實(shí)現(xiàn)接口來(lái)定制Validator,一種是使用約束注解。胖哥覺(jué)得注解可以滿足絕大部分的需求,所以建議使用注解來(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 常見(jiàn)問(wèn)題

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

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

          上面為了校驗(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ù)中的元素不生效的問(wèn)題

          就像下面的寫法,方法的參數(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)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费高清不卡a无码 | 黄片成人在线观看 | 日本色情免费视频 | 欧美+日产+中文 | 99热精品88 |