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

          SpringBoot:如何優(yōu)雅地進行響應(yīng)數(shù)據(jù)封裝、異常處理?

          共 9354字,需瀏覽 19分鐘

           ·

          2022-08-04 14:10

          背景

          越來越多的項目開始基于前后端分離的模式進行開發(fā),這對后端接口的報文格式便有了一定的要求。通常,我們會采用JSON格式作為前后端交換數(shù)據(jù)格式,從而減少溝通成本等。

          這篇文章,就帶大家了解一下基于SpringBoot框架來封裝返回報文以及統(tǒng)一異常處理。

          報文基本格式

          一般報文格式通常會包含狀態(tài)碼、狀態(tài)描述(或錯誤提示信息)、業(yè)務(wù)數(shù)據(jù)等信息。在此基礎(chǔ)上,不同的架構(gòu)師、項目搭建者可能會有所調(diào)整。但從整體上來說,基本上都是大同小異。

          在SpringBoot項目中,通常接口返回的報文中至少包含三個屬性:

          • code:請求接口的返回碼,成功或者異常等返回編碼,例如定義請求成功。
          • message:請求接口的描述,也就是對返回編碼的描述。
          • data:請求接口成功,返回的業(yè)務(wù)數(shù)據(jù)。

          示例報文如下:

          {
            "code":200,
            "message":"SUCCESS",
            "data":{
              "info":"測試成功"
            }
          }

          在上述報文格式中,不同的設(shè)計者是會有一些分歧的,特別是code值的定義。如果完全基于RESTful API設(shè)計的話,code字段可能就不需要存在了,而是通過HTTP協(xié)議中提供的GET、POST、PUT、DELETE操作等來完成資源的訪問。

          但在實踐中,不論是出于目前國內(nèi)大多數(shù)程序員的習(xí)慣,還是受限于HTTP協(xié)議提供的操作方法的局限性,很少完全遵照RESTful API方式進行設(shè)計。通常都是通過自定義Code值的形式來賦予它業(yè)務(wù)意義或業(yè)務(wù)錯誤編碼。

          雖然可以不用完全遵守RESTful API風(fēng)格來定義Code,在Code值的自定義中,也存在兩種形式:遵循HTTP狀態(tài)碼和自主定義。

          像上面的示例,用200表示返回成功,這就是遵循HTTP響應(yīng)狀態(tài)碼的形式來返回,比如還有其他的400、401、404、500等。當然,還有完全自主定義的,比如用0表示成功,1表示失敗,然后再跟進通用編碼、業(yè)務(wù)分類編碼等進行定義。

          在此,筆者暫不評論每種形式的好壞,只列舉了常規(guī)的幾種形式,大家了解對應(yīng)的情況,做到心中有數(shù),有所選擇即可。

          響應(yīng)參數(shù)封裝實踐

          創(chuàng)建一個SpringBoot項目,并引入Lombok依賴(精簡代碼),對應(yīng)的核心依賴如下:

              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.projectlombok</groupId>
                      <artifactId>lombok</artifactId>
                  </dependency>
              </dependencies>

          創(chuàng)建枚舉類,用于定義返回的錯誤碼:

          @Getter
          @AllArgsConstructor
          public enum ResponseCodeEnums {

           SUCCESS(200, "success"),
           FAIL(500, "failed"),

           HTTP_STATUS_200(200, "ok"),
           HTTP_STATUS_400(400, "request error"),
           HTTP_STATUS_401(401, "no authentication"),
           HTTP_STATUS_403(403, "no authorities"),
           HTTP_STATUS_500(500, "server error");

           private final int code;

           private final String message;
          }

          這里只定義了一些通用的、基于的HTTP響應(yīng)狀態(tài)碼,業(yè)務(wù)相關(guān)的編碼可根據(jù)業(yè)務(wù)需求進行定義。

          定義統(tǒng)一返回結(jié)果實體類:

          @Data
          public class ResponseInfo<T> {

           /**
            * 狀態(tài)碼
            */
           protected int code;

           /**
            * 響應(yīng)信息
            */
           protected String message;

           /**
            * 返回數(shù)據(jù)
            */
           private T data;

           public static <T> ResponseInfo<T> success() {
            return new ResponseInfo<>();
           }

           public static <T> ResponseInfo<T> success(T data) {
            return new ResponseInfo<>(data);
           }

           public static <T> ResponseInfo<T> fail(String message) {
            return new ResponseInfo<>(ResponseCodeEnums.FAIL.getCode(), message);
           }

           public ResponseInfo() {
            this.code = ResponseCodeEnums.SUCCESS.getCode();
            this.message = ResponseCodeEnums.SUCCESS.getMessage();
           }

           public ResponseInfo(ResponseCodeEnums statusEnums) {
            this.code = statusEnums.getCode();
            this.message = statusEnums.getMessage();
           }

           /**
            * 若沒有數(shù)據(jù)返回,可以人為指定狀態(tài)碼和提示信息
            */
           public ResponseInfo(int code, String msg) {
            this.code = code;
            this.message = msg;
           }

           /**
            * 有數(shù)據(jù)返回時,狀態(tài)碼為200,默認提示信息為“操作成功!”
            */
           public ResponseInfo(T data) {
            this.data = data;
            this.code = ResponseCodeEnums.SUCCESS.getCode();
            this.message = ResponseCodeEnums.SUCCESS.getMessage();
           }

           /**
            * 有數(shù)據(jù)返回,狀態(tài)碼為 200,人為指定提示信息
            */
           public ResponseInfo(T data, String msg) {
            this.data = data;
            this.code = ResponseCodeEnums.SUCCESS.getCode();
            this.message = msg;
           }
          }

          在ResponseInfo中運用了泛型和公共方法、構(gòu)造方法的封裝,方便在業(yè)務(wù)中使用。示例中只提供了部分方法的封裝,根據(jù)自身業(yè)務(wù)場景和需要可進一步封裝。

          統(tǒng)一報文封裝在接口中的使用:

          @Slf4j
          @RestController
          public class TestController {

           @RequestMapping("/calc")
           public ResponseInfo<String> calc(Integer id) {
            try {
             // 模擬異常業(yè)務(wù)代碼
             int num = 1 / id;
             log.info("計算結(jié)果num={}", num);
             return ResponseInfo.success();
            } catch (Exception e) {
             return ResponseInfo.fail("系統(tǒng)異常,請聯(lián)系管理員!");
            }
           }
          }

          在瀏覽器中訪問:http://localhost:8080/calc,返回結(jié)果如下:

          {
              "code": 500,
              "message""系統(tǒng)異常,請聯(lián)系管理員!",
              "data": null
          }

          這是因為沒傳遞id參數(shù),導(dǎo)致業(yè)務(wù)拋異常,走異常報文返回。

          在瀏覽器中訪問:http://localhost:8080/calc?id=1,返回結(jié)果如下:

          {
              "code": 200,
              "message""success",
              "data": null
          }

          正常返回結(jié)果。

          統(tǒng)一異常處理

          在上述實例中,我們通過try...catch的形式捕獲異常,并進行處理。在SpringBoot中,我們可以通過RestControllerAdvice注解來定義全局異常處理,這樣就無需每處都try...catch了。

          @Slf4j
          @RestControllerAdvice
          public class ExceptionHandlerAdvice {

           /**
            * 參數(shù)格式異常處理
            */
           @ExceptionHandler({IllegalArgumentException.class})
           @ResponseStatus(HttpStatus.BAD_REQUEST)
           public ResponseInfo<String> badRequestException(IllegalArgumentException ex) {
            log.error("參數(shù)格式不合法:{}", ex.getMessage());
            return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + """參數(shù)格式不符!");
           }

           /**
            * 權(quán)限不足異常處理
            */
           @ExceptionHandler({AccessDeniedException.class})
           @ResponseStatus(HttpStatus.FORBIDDEN)
           public ResponseInfo<String> badRequestException(AccessDeniedException ex) {
            return new ResponseInfo<>(HttpStatus.FORBIDDEN.value() + "", ex.getMessage());
           }

           /**
            * 參數(shù)缺失異常處理
            */
           @ExceptionHandler({MissingServletRequestParameterException.class})
           @ResponseStatus(HttpStatus.BAD_REQUEST)
           public ResponseInfo<String> badRequestException(Exception ex) {
            return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + """缺少必填參數(shù)!");
           }

           /**
            * 空指針異常
            */
           @ExceptionHandler(NullPointerException.class)
           @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
           public ResponseInfo<String> handleTypeMismatchException(NullPointerException ex) {
            log.error("空指針異常,{}", ex.getMessage());
            return ResponseInfo.fail("空指針異常");
           }

           @ExceptionHandler(Exception.class)
           @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
           public ResponseInfo<String> handleUnexpectedServer(Exception ex) {
            log.error("系統(tǒng)異常:", ex);
            return ResponseInfo.fail("系統(tǒng)發(fā)生異常,請聯(lián)系管理員");
           }

           /**
            * 系統(tǒng)異常處理
            */
           @ExceptionHandler(Throwable.class)
           @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
           public ResponseInfo<String> exception(Throwable throwable) {
            log.error("系統(tǒng)異常", throwable);
            return new ResponseInfo<>(HttpStatus.INTERNAL_SERVER_ERROR.value() + "系統(tǒng)異常,請聯(lián)系管理員!");
           }
          }

          在上述方法中,對一些常見的異常進行了統(tǒng)一處理。通常情況下,根據(jù)業(yè)務(wù)需要還會定義業(yè)務(wù)異常,并對業(yè)務(wù)異常進行處理,大家可以根據(jù)自己項目中異常的使用情況進行拓展。

          關(guān)于@RestControllerAdvice的幾點說明:

          • @RestControllerAdvice注解包含了@Component注解,會把被注解的類作為組件交給Spring來管理。
          • @RestControllerAdvice注解包含了@ResponseBody注解,異常處理完之后給調(diào)用方輸出一個JSON格式的封裝數(shù)據(jù)。
          • @RestControllerAdvice注解有一個basePackages屬性,該屬性用來攔截哪個包中的異常信息,一般不指定,攔截項目工程中的所有異常。
          • 在方法上通過@ExceptionHandler注解來指定具體的異常,在方法中處理該異常信息,最后將結(jié)果通過統(tǒng)一的JSON結(jié)構(gòu)體返回給調(diào)用者。

          重新定義一個接口:

           @RequestMapping("/calc1")
           public ResponseInfo<String> calc1(Integer id) {
            // 模擬異常業(yè)務(wù)代碼
            int num = 1 / id;
            log.info("計算結(jié)果num={}", num);
            return ResponseInfo.success();
           }

          在請求的時候,不傳遞id值,即在瀏覽器中訪問:

          {
              "code": 500,
              "message""空指針異常",
              "data": null
          }

          可以看到統(tǒng)一異常處理對空指針異常進行了攔截處理,并返回了ExceptionHandlerAdvice中定義的統(tǒng)一報文格式。

          小結(jié)

          在使用SpringBoot或其他項目中,統(tǒng)一的報文格式和統(tǒng)一的異常處理都是必須的。本篇文章介紹了基于SpringBoot的實現(xiàn),如果你的項目中采用了其他的技術(shù)棧,則可考慮對應(yīng)的處理方式。同時,日常中很多類似的功能都可以統(tǒng)一進行處理,避免大量無效的硬編碼。

          isEmpty 和 isBlank 的用法區(qū)別,至少一半的人答不上來...

          2022-08-02

          Spring Boot + EasyExcel導(dǎo)入導(dǎo)出,簡直太好用了!

          2022-08-01

          代碼真的越改越爛。。

          2022-07-31

          Redis中的布隆過濾器與布谷鳥過濾器,你了解多少?

          2022-07-30

          為了實現(xiàn)CI/CD,先來定制一個Docker鏡像【實戰(zhàn)精華篇】

          2022-07-29

          Linux安裝Docker完整教程

          2022-07-28




          如果你覺得這篇文章不錯,那么,下篇通常會更好。備注“公眾號”添加微信好友(微信號:zhuan2quan)

          ▲ 按關(guān)注”程序新視界“,洞察技術(shù)內(nèi)幕


          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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 | 亚洲色图88 | 欧美三级片网 | 亚洲无吗在线播放 |