<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>

          Java如何優(yōu)雅地實(shí)現(xiàn)接口數(shù)據(jù)校驗(yàn)

          共 10159字,需瀏覽 21分鐘

           ·

          2020-12-12 08:32


          本篇文章給大家分享平時(shí)開(kāi)發(fā)中總結(jié)的一點(diǎn)小技巧!在工作中寫(xiě)過(guò)Java程序的朋友都知道,目前使用Java開(kāi)發(fā)服務(wù)最主流的方式就是通過(guò)Spring MVC定義一個(gè)Controller層接口,并將接口請(qǐng)求或返回參數(shù)分別定義在一個(gè)Java實(shí)體類中,這樣Spring MVC在接收到Http請(qǐng)求(POST/GET)后,就會(huì)自動(dòng)將請(qǐng)求報(bào)文自動(dòng)映射成一個(gè)Java對(duì)象。這樣的代碼通常是這樣寫(xiě)的:


          @RestController
          public?class?OrderController?{

          ????@Autowired
          ????private?OrderService?orderServiceImpl;

          ????@PostMapping("/createOrder")
          ????public?CreateOrderBO?validationTest(@Validated?CreateOrderDTO?createOrderDTO)?{
          ????????return?orderServiceImpl.createOrder(createOrderDTO);
          ????}
          }


          這樣的代碼相信大家并不陌生,但在后續(xù)的邏輯實(shí)現(xiàn)過(guò)程中卻會(huì)遇到這樣的問(wèn)題:“在接收請(qǐng)求參數(shù)后如何實(shí)現(xiàn)報(bào)文對(duì)象數(shù)據(jù)值的合法性校驗(yàn)?”。一些同學(xué)也可能認(rèn)為這并不是什么問(wèn)題,因?yàn)榫唧w某個(gè)參數(shù)字段是否為空、值的取值是否在約定范圍、格式是否合法等等,在業(yè)務(wù)代碼中校驗(yàn)就好了。例如可以在Service實(shí)現(xiàn)類中對(duì)報(bào)文格式進(jìn)行各種if-else的數(shù)據(jù)校驗(yàn)。


          從功能上說(shuō)冗余的if-else代碼沒(méi)啥毛病,但從代碼的優(yōu)雅性來(lái)說(shuō)冗長(zhǎng)的if-else代碼會(huì)顯得非常臃腫。接下來(lái)的內(nèi)容將給大家介紹一種處理此類問(wèn)題的實(shí)用方法。具體將從以下幾個(gè)方面進(jìn)行介紹:


          • 使用@Validated注解實(shí)現(xiàn)Controller接口層數(shù)據(jù)直接綁定校驗(yàn);

          • 擴(kuò)展約束性注解實(shí)現(xiàn)數(shù)據(jù)取值范圍的校驗(yàn);

          • 更加靈活的對(duì)象數(shù)據(jù)合法性校驗(yàn)工具類封裝;

          • 數(shù)據(jù)合法性校驗(yàn)結(jié)果異常統(tǒng)一返回處理;


          Controller接口層數(shù)據(jù)綁定校驗(yàn)


          實(shí)際上在Java開(kāi)發(fā)中目前普通使用的Bean數(shù)據(jù)校驗(yàn)工具是"hibernate-validator",它是一個(gè)hibernete獨(dú)立的jar包,所以使用這個(gè)jar包并不需要一定要集成Hibernete框架。該jar包主要實(shí)現(xiàn)并擴(kuò)展了javax.validation(是一個(gè)基于JSR-303標(biāo)準(zhǔn)開(kāi)發(fā)出來(lái)的Bean校驗(yàn)規(guī)范)接口。


          由于Spring Boot在內(nèi)部默認(rèn)集成了"hibernate-validator",所以使用Spring Boot構(gòu)建的Java工程可以直接使用相關(guān)注解來(lái)實(shí)現(xiàn)Bean的數(shù)據(jù)校驗(yàn)。例如我們最常編寫(xiě)的Controller層接口參數(shù)對(duì)象,可以在定義Bean類時(shí)直接編寫(xiě)這樣的代碼:


          @Data
          public?class?CreateOrderDTO?{

          ????@NotNull(message?=?"訂單號(hào)不能為空")
          ????private?String?orderId;
          ????@NotNull(message?=?"訂單金額不能為空")
          ????@Min(value?=?1,?message?=?"訂單金額不能小于0")
          ????private?Integer?amount;
          ????@Pattern(regexp?=?"^1[3|4|5|7|8][0-9]{9}$",?message?=?"用戶手機(jī)號(hào)不合法")
          ????private?String?mobileNo;
          ????private?String?orderType;
          ????private?String?status;
          }


          如上所示代碼,我們可以使用@NotNull注解來(lái)約束該字段必須不能為空,也可以使用@Min注解來(lái)約束字段的最小取值,或者還可以通過(guò)@Pattern注解來(lái)使用正則表達(dá)式來(lái)約束字段的格式(如手機(jī)號(hào)格式)等等。


          以上這些注解都是“hibernate-validator”依賴包默認(rèn)提供的,更多常用的注解還有很多,例如:


          利用這些約束注解,我們就可以很輕松的搞定接口數(shù)據(jù)校驗(yàn),而不需要在業(yè)務(wù)邏輯中編寫(xiě)大量的if-else來(lái)進(jìn)行數(shù)據(jù)合法性校驗(yàn)。而定義好Bean參數(shù)對(duì)象并使用相關(guān)注解實(shí)現(xiàn)參數(shù)值約束后,在Controller層接口定義中只需要使用@Validated注解就可以實(shí)現(xiàn)在接收參數(shù)后自動(dòng)進(jìn)行數(shù)據(jù)綁定校驗(yàn)了,具體代碼如下:


          @PostMapping("/createOrder")
          public?CreateOrderBO?validationTest(@Validated?CreateOrderDTO?createOrderDTO)?{
          ????return?orderServiceImpl.createOrder(createOrderDTO);
          }


          如上所示,在Controller層中通過(guò)Spring提供的@Validated注解可以自動(dòng)實(shí)現(xiàn)數(shù)據(jù)Bean的綁定校驗(yàn),如果數(shù)據(jù)異常則會(huì)統(tǒng)一拋出校驗(yàn)異常!



          約束性注解擴(kuò)展


          在“hibernate-validator”依賴jar包中,雖然提供了很多很方便的約束注解,但是也有不滿足某些實(shí)際需要的情況,例如我們想針對(duì)參數(shù)中的某個(gè)值約定其值的枚舉范圍,如orderType訂單類型只允許傳“pay”、“refund”兩種值,那么現(xiàn)有的約束注解可能就沒(méi)有特別適用的了。此外,如果對(duì)這樣的枚舉值,我們還想在約束定義中直接匹配代碼中的枚舉定義,以更好地統(tǒng)一接口參數(shù)與業(yè)務(wù)邏輯的枚舉定義。那么這種情況下,我們還可以自己擴(kuò)展定義相應(yīng)地約束注解邏輯。

          接下來(lái)我們定義新的約束注解@EnumValue,來(lái)實(shí)現(xiàn)上面我們所說(shuō)的效果,具體代碼如下:


          @Target({METHOD,?FIELD,?ANNOTATION_TYPE,?CONSTRUCTOR,?PARAMETER})
          @Retention(RUNTIME)
          @Documented
          @Constraint(validatedBy?=?{EnumValueValidator.class})
          public?@interface?EnumValue?{

          ????//默認(rèn)錯(cuò)誤消息
          ????String?message()?default?"必須為指定值";

          ????//支持string數(shù)組驗(yàn)證
          ????String[]?strValues()?default?{};

          ????//支持int數(shù)組驗(yàn)證
          ????int[]?intValues()?default?{};

          ????//支持枚舉列表驗(yàn)證
          ????Class[]?enumValues()?default?{};

          ????//分組
          ????Class[]?groups()?default?{};

          ????//負(fù)載
          ????Class[]?payload()?default?{};

          ????//指定多個(gè)時(shí)使用
          ????@Target({FIELD,?METHOD,?PARAMETER,?ANNOTATION_TYPE})
          ????@Retention(RUNTIME)
          ????@Documented
          ????@interface?List?{
          ????????EnumValue[]?value();
          ????}

          ????/**
          ?????*?校驗(yàn)類邏輯定義
          ?????*/

          ????class?EnumValueValidator?implements?ConstraintValidator<EnumValue,?Object>?{

          ????????//字符串類型數(shù)組
          ????????private?String[]?strValues;
          ????????//int類型數(shù)組
          ????????private?int[]?intValues;
          ????????//枚舉類
          ????????private?Class[]?enumValues;

          ????????/**
          ?????????*?初始化方法
          ?????????*
          ?????????*?@param?constraintAnnotation
          ?????????*/

          ????????@Override
          ????????public?void?initialize(EnumValue?constraintAnnotation)?{
          ????????????strValues?=?constraintAnnotation.strValues();
          ????????????intValues?=?constraintAnnotation.intValues();
          ????????????enumValues?=?constraintAnnotation.enumValues();
          ????????}

          ????????/**
          ?????????*?校驗(yàn)方法
          ?????????*
          ?????????*?@param?value
          ?????????*?@param?context
          ?????????*?@return
          ?????????*/

          ????????@SneakyThrows
          ????????@Override
          ????????public?boolean?isValid(Object?value,?ConstraintValidatorContext?context)?{
          ????????????//針對(duì)字符串?dāng)?shù)組的校驗(yàn)匹配
          ????????????if?(strValues?!=?null?&&?strValues.length?>?0)?{
          ????????????????if?(value?instanceof?String)?{
          ????????????????????for?(String?s?:?strValues)?{//判斷值類型是否為Integer類型
          ????????????????????????if?(s.equals(value))?{
          ????????????????????????????return?true;
          ????????????????????????}
          ????????????????????}
          ????????????????}
          ????????????}
          ????????????//針對(duì)整型數(shù)組的校驗(yàn)匹配
          ????????????if?(intValues?!=?null?&&?intValues.length?>?0)?{
          ????????????????if?(value?instanceof?Integer)?{//判斷值類型是否為Integer類型
          ????????????????????for?(Integer?s?:?intValues)?{
          ????????????????????????if?(s?==?value)?{
          ????????????????????????????return?true;
          ????????????????????????}
          ????????????????????}
          ????????????????}
          ????????????}
          ????????????//針對(duì)枚舉類型的校驗(yàn)匹配
          ????????????if?(enumValues?!=?null?&&?enumValues.length?>?0)?{
          ????????????????for?(Class?cl?:?enumValues)?{
          ????????????????????if?(cl.isEnum())?{
          ????????????????????????//枚舉類驗(yàn)證
          ????????????????????????Object[]?objs?=?cl.getEnumConstants();
          ????????????????????????//這里需要注意,定義枚舉時(shí),枚舉值名稱統(tǒng)一用value表示
          ????????????????????????Method?method?=?cl.getMethod("getValue");
          ????????????????????????for?(Object?obj?:?objs)?{
          ????????????????????????????Object?code?=?method.invoke(obj,?null);
          ????????????????????????????if?(value.equals(code.toString()))?{
          ????????????????????????????????return?true;
          ????????????????????????????}
          ????????????????????????}
          ????????????????????}
          ????????????????}
          ????????????}
          ????????????return?false;
          ????????}
          ????}
          }


          如上所示的@EnumValue約束注解,是一個(gè)非常實(shí)用的擴(kuò)展,通過(guò)該注解我們可以實(shí)現(xiàn)對(duì)參數(shù)取值范圍(不是大小范圍)的約束,它支持對(duì)int、string以及enum三種數(shù)據(jù)類型的約束,具體使用方式如下:

          /**
          ?*?定制化注解,支持參數(shù)值與指定類型數(shù)組列表值進(jìn)行匹配(缺點(diǎn)是需要將枚舉值寫(xiě)死在字段定義的注解中)
          ?*/

          @EnumValue(strValues?=?{"pay",?"refund"},?message?=?"訂單類型錯(cuò)誤")
          private?String?orderType;
          /**
          ?*?定制化注解,實(shí)現(xiàn)參數(shù)值與枚舉列表的自動(dòng)匹配校驗(yàn)(能更好地與實(shí)際業(yè)務(wù)開(kāi)發(fā)匹配)
          ?*/

          @EnumValue(enumValues?=?Status.class,?message?=?"狀態(tài)值不在指定范圍")
          private?String?status;


          如上所示代碼,該擴(kuò)展注解既可以使用strValues或intValues屬性來(lái)編程列舉取值范圍,也可以直接通過(guò)enumValues來(lái)綁定枚舉定義。但是需要注意,處于通用考慮,具體枚舉定義的屬性的名稱要統(tǒng)一匹配為value、desc,例如Status枚舉定義如下:


          public?enum?Status?{
          ????PROCESSING(1,?"處理中"),
          ????SUCCESS(2,?"訂單已完成");
          ????Integer?value;
          ????String?desc;

          ????Status(Integer?value,?String?desc)?{
          ????????this.value?=?value;
          ????????this.desc?=?desc;
          ????}

          ????public?Integer?getValue()?{
          ????????return?value;
          ????}

          ????public?String?getDesc()?{
          ????????return?desc;
          ????}
          }


          通過(guò)注解擴(kuò)展,就能實(shí)現(xiàn)更多方便的約束性注解!



          更加靈活數(shù)據(jù)校驗(yàn)工具類封裝


          除了上面直接在Controller層使用@Validated進(jìn)行綁定數(shù)據(jù)校驗(yàn)外,在有些情況,例如你的參數(shù)對(duì)象中的某個(gè)字段是一個(gè)復(fù)合對(duì)象,或者業(yè)務(wù)層的某個(gè)方法所定義的入?yún)?duì)象也需要進(jìn)行數(shù)據(jù)合法性校驗(yàn),那么這種情況下如何實(shí)現(xiàn)像Controller層一樣的校驗(yàn)效果呢?

          需要說(shuō)明在這種情況下@Validated已經(jīng)無(wú)法直接使用了,因?yàn)?span style="color: rgb(60, 60, 60);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;letter-spacing: 1px;caret-color: rgb(60, 60, 60);background-color: rgb(255, 255, 255);">@Validated注解發(fā)揮作用主要是Spring MVC在接收參數(shù)的過(guò)程中實(shí)現(xiàn)了自動(dòng)數(shù)據(jù)綁定校驗(yàn),而在普通的業(yè)務(wù)方法或者復(fù)合參數(shù)對(duì)象中是沒(méi)有辦法直接綁定校驗(yàn)的。這種情況下,我們可以通過(guò)定義ValidateUtils工具類來(lái)實(shí)現(xiàn)一樣的校驗(yàn)效果,具體代碼如下:


          public?class?ValidatorUtils?{

          ????private?static?Validator?validator?=?Validation.buildDefaultValidatorFactory().getValidator();

          ????/**
          ?????*?bean整體校驗(yàn),有不合規(guī)范,拋出第1個(gè)違規(guī)異常
          ?????*/

          ????public?static?void?validate(Object?obj,?Class...?groups)?{
          ????????Set>?resultSet?=?validator.validate(obj,?groups);
          ????????if?(resultSet.size()?>?0)?{
          ????????????//如果存在錯(cuò)誤結(jié)果,則將其解析并進(jìn)行拼湊后異常拋出
          ????????????List?errorMessageList?=?resultSet.stream().map(o?->?o.getMessage()).collect(Collectors.toList());
          ????????????StringBuilder?errorMessage?=?new?StringBuilder();
          ????????????errorMessageList.stream().forEach(o?->?errorMessage.append(o?+?";"));
          ????????????throw?new?IllegalArgumentException(errorMessage.toString());
          ????????}
          ????}
          }


          如上所示,我們定義了一個(gè)基于"javax.validation"接口的工具類實(shí)現(xiàn),這樣就可以在非@Validated直接綁定校驗(yàn)的場(chǎng)景中通過(guò)校驗(yàn)工具類來(lái)實(shí)現(xiàn)對(duì)Bean對(duì)象約束注解的校驗(yàn)處理,具體使用代碼如下:


          public?boolean?orderCheck(OrderCheckBO?orderCheckBO)?{
          ????//對(duì)參數(shù)對(duì)象進(jìn)行數(shù)據(jù)校驗(yàn)
          ????ValidatorUtils.validate(orderCheckBO);
          ????return?true;
          }


          而方法入?yún)?duì)象則還是可以繼續(xù)使用前面我們介紹的約束性注解進(jìn)行約定,例如上述方法的入?yún)?duì)象定義如下:


          @Data
          @Builder
          public?class?OrderCheckBO?{

          ????@NotNull(message?=?"訂單號(hào)不能為空")
          ????private?String?orderId;
          ????@Min(value?=?1,?message?=?"訂單金額不能小于0")
          ????private?Integer?orderAmount;
          ????@NotNull(message?=?"創(chuàng)建人不能為空")
          ????private?String?operator;
          ????@NotNull(message?=?"操作時(shí)間不能為空")
          ????private?String?operatorTime;
          }


          這樣在編程體驗(yàn)上就可以整體上保持一致!



          數(shù)據(jù)合法性校驗(yàn)結(jié)果異常統(tǒng)一處理


          通過(guò)前面我們所講的各種約束注解,我們實(shí)現(xiàn)了對(duì)Controller層接口以及業(yè)務(wù)方法參數(shù)對(duì)象的統(tǒng)一數(shù)據(jù)校驗(yàn)。而為了保持校驗(yàn)異常處理的統(tǒng)一處理和錯(cuò)誤報(bào)文統(tǒng)一輸出,我們還可以定義通用的異常處理機(jī)制,來(lái)保證各類數(shù)據(jù)校驗(yàn)錯(cuò)誤都能以統(tǒng)一錯(cuò)誤格式反饋給調(diào)用方。具體代碼如下:


          @Slf4j
          @ControllerAdvice
          public?class?GlobalExceptionHandler?{
          ????/**
          ?????*?統(tǒng)一處理參數(shù)校驗(yàn)錯(cuò)誤異常(非Spring接口數(shù)據(jù)綁定驗(yàn)證)
          ?????*
          ?????*?@param?response
          ?????*?@param?e
          ?????*?@return
          ?????*/

          ????@ExceptionHandler(BindException.class)
          ????@ResponseBody
          ????public?ResponseResult?processValidException(HttpServletResponse?response,?BindException?e)?{
          ????????response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
          ????????//獲取校驗(yàn)錯(cuò)誤結(jié)果信息,并將信息組裝
          ????????List?errorStringList?=?e.getBindingResult().getAllErrors()
          ????????????????.stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
          ????????String?errorMessage?=?String.join(";?",?errorStringList);
          ????????response.setContentType("application/json;charset=UTF-8");
          ????????log.error(e.toString()?+?"_"?+?e.getMessage(),?e);
          ????????return?ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(),
          ????????????????errorMessage);
          ????}

          ????/**
          ?????*?統(tǒng)一處理參數(shù)校驗(yàn)錯(cuò)誤異常
          ?????*
          ?????*?@param?response
          ?????*?@param?e
          ?????*?@return
          ?????*/

          ????@ExceptionHandler(IllegalArgumentException.class)
          ????@ResponseBody
          ????public?ResponseResult?processValidException(HttpServletResponse?response,?IllegalArgumentException?e)?{
          ????????response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
          ????????String?errorMessage?=?String.join(";?",?e.getMessage());
          ????????response.setContentType("application/json;charset=UTF-8");
          ????????log.error(e.toString()?+?"_"?+?e.getMessage(),?e);
          ????????return?ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(),
          ????????????????errorMessage);
          ????}

          ????...
          }


          如上所示,我們定義了針對(duì)前面兩種數(shù)據(jù)校驗(yàn)方式的統(tǒng)一異常處理機(jī)制,這樣數(shù)據(jù)校驗(yàn)的錯(cuò)誤信息就能通過(guò)統(tǒng)一的報(bào)文格式反饋給調(diào)用端,從而實(shí)現(xiàn)接口數(shù)據(jù)報(bào)文的統(tǒng)一返回!


          其中通用的接口參數(shù)對(duì)象ResponseResult的代碼定義如下:


          @Data
          @Builder
          @NoArgsConstructor
          @AllArgsConstructor
          @JsonPropertyOrder({"code",?"message",?"data"})
          public?class?ResponseResult<T>?implements?Serializable?{

          ????private?static?final?long?serialVersionUID?=?1L;

          ????/**
          ?????*?返回的對(duì)象
          ?????*/

          ????@JsonInclude(JsonInclude.Include.NON_NULL)
          ????private?T?data;
          ????/**
          ?????*?返回的編碼
          ?????*/

          ????private?Integer?code;
          ????/**
          ?????*?返回的信息
          ?????*/

          ????private?String?message;

          ????/**
          ?????*?@param?data?返回的數(shù)據(jù)
          ?????*?@param???返回的數(shù)據(jù)類型
          ?????*?@return?響應(yīng)結(jié)果
          ?????*/

          ????public?static??ResponseResult?OK(T?data)?{
          ????????return?packageObject(data,?GlobalCodeEnum.GL_SUCC_0);
          ????}

          ????/**
          ?????*?自定義系統(tǒng)異常信息
          ?????*
          ?????*?@param?code
          ?????*?@param?message?自定義消息
          ?????*?@param?
          ?????*?@return
          ?????*/

          ????public?static??ResponseResult?systemException(Integer?code,?String?message)?{
          ????????return?packageObject(null,?code,?message);
          ????}
          }


          當(dāng)然,這樣的統(tǒng)一報(bào)文格式也不僅僅只處理異常返回,正常的數(shù)據(jù)報(bào)文格式也可以通過(guò)該對(duì)象來(lái)進(jìn)行統(tǒng)一封裝!


          本文內(nèi)容從實(shí)用的角度給大家演示了,如何在日常工作中編寫(xiě)通用的數(shù)據(jù)校驗(yàn)邏輯,希望能對(duì)大家有所幫助,如果覺(jué)得還不錯(cuò),可以給點(diǎn)支持,轉(zhuǎn)發(fā)+在看!感謝閱讀!



          —————END—————


          瀏覽 47
          點(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>
                  欧美高清视频99 | 黃色一级一片免费播放 | 欧美黄色性爱 | av京东热 | 麻豆传剧原创在线观看 |