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

          Spring Boot 中的全局異常處理

          共 7353字,需瀏覽 15分鐘

           ·

          2021-02-06 10:16

          本來已收錄到我寫的10萬字Springboot經(jīng)典學(xué)習(xí)筆記中,筆記在持續(xù)更新……文末有領(lǐng)取方式

          在項目開發(fā)過程中,不管是對底層數(shù)據(jù)庫的操作過程,還是業(yè)務(wù)層的處理過程,還是控制層的處理過程,都不可避免會遇到各種可預(yù)知的、不可預(yù)知的異常需要處理。如果對每個過程都單獨作異常處理,那系統(tǒng)的代碼耦合度會變得很高,此外,開發(fā)工作量也會加大而且不好統(tǒng)一,這也增加了代碼的維護成本。

          針對這種實際情況,我們需要將所有類型的異常處理從各處理過程解耦出來,這樣既保證了相關(guān)處理過程的功能單一,也實現(xiàn)了異常信息的統(tǒng)一處理和維護。同時,我們也不希望直接把異常拋給用戶,應(yīng)該對異常進行處理,對錯誤信息進行封裝,然后返回一個友好的信息給用戶。這節(jié)主要總結(jié)一下項目中如何使用 Spring Boot 如何攔截并處理全局的異常。

          1. 定義返回的統(tǒng)一 json 結(jié)構(gòu)

          前端或者其他服務(wù)請求本服務(wù)的接口時,該接口需要返回對應(yīng)的 json 數(shù)據(jù),一般該服務(wù)只需要返回請求著需要的參數(shù)即可,但是在實際項目中,我們需要封裝更多的信息,比如狀態(tài)碼 code、相關(guān)信息 msg 等等,這一方面是在項目中可以有個統(tǒng)一的返回結(jié)構(gòu),整個項目組都適用,另一方面是方便結(jié)合全局異常處理信息,因為異常處理信息中一般我們需要把狀態(tài)碼和異常內(nèi)容反饋給調(diào)用方。
          這個統(tǒng)一的 json 結(jié)構(gòu)這可以參考第02課:Spring Boot 返回 JSON 數(shù)據(jù)及數(shù)據(jù)封裝中封裝的統(tǒng)一 json 結(jié)構(gòu),本節(jié)內(nèi)容我們簡化一下,只保留狀態(tài)碼 code 和異常信息 msg即可。如下:

          public?class?JsonResult?{
          ????/**
          ?????*?異常碼
          ?????*/

          ????protected?String?code;

          ????/**
          ?????*?異常信息
          ?????*/

          ????protected?String?msg;
          ?
          ????public?JsonResult()?{
          ????????this.code?=?"200";
          ????????this.msg?=?"操作成功";
          ????}
          ????
          ????public?JsonResult(String?code,?String?msg)?{
          ????????this.code?=?code;
          ????????this.msg?=?msg;
          ????}
          ?//?get?set
          }

          2. 處理系統(tǒng)異常

          新建一個 GlobalExceptionHandler 全局異常處理類,然后加上?@ControllerAdvice?注解即可攔截項目中拋出的異常,如下:

          @ControllerAdvice
          @ResponseBody
          public?class?GlobalExceptionHandler?{
          ?//?打印log
          ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(GlobalExceptionHandler.class);
          ????//?……
          }

          我們點開?@ControllerAdvice?注解可以看到,@ControllerAdvice?注解包含了?@Component?注解,說明在 Spring Boot 啟動時,也會把該類作為組件交給 Spring 來管理。除此之外,該注解還有個?basePackages?屬性,該屬性是用來攔截哪個包中的異常信息,一般我們不指定這個屬性,我們攔截項目工程中的所有異常。@ResponseBody?注解是為了異常處理完之后給調(diào)用方輸出一個 json 格式的封裝數(shù)據(jù)。
          在項目中如何使用呢?Spring Boot 中很簡單,在方法上通過?@ExceptionHandler?注解來指定具體的異常,然后在方法中處理該異常信息,最后將結(jié)果通過統(tǒng)一的 json 結(jié)構(gòu)體返回給調(diào)用者。下面我們舉幾個例子來說明如何來使用。

          2.1 處理參數(shù)缺失異常

          在前后端分離的架構(gòu)中,前端請求后臺的接口都是通過 rest 風(fēng)格來調(diào)用,有時候,比如 POST 請求 需要攜帶一些參數(shù),但是往往有時候參數(shù)會漏掉。另外,在微服務(wù)架構(gòu)中,涉及到多個微服務(wù)之間的接口調(diào)用時,也可能出現(xiàn)這種情況,此時我們需要定義一個處理參數(shù)缺失異常的方法,來給前端或者調(diào)用方提示一個友好信息。

          參數(shù)缺失的時候,會拋出?HttpMessageNotReadableException,我們可以攔截該異常,做一個友好處理,如下:

          /**
          *?缺少請求參數(shù)異常
          *?@param?ex?HttpMessageNotReadableException
          *?@return
          */

          @ExceptionHandler(MissingServletRequestParameterException.class)
          @ResponseStatus(value?
          =?HttpStatus.BAD_REQUEST)
          public?JsonResult?handleHttpMessageNotReadableException(
          ????MissingServletRequestParameterException?ex)
          ?
          {
          ????logger.error("缺少請求參數(shù),{}",?ex.getMessage());
          ????return?new?JsonResult("400",?"缺少必要的請求參數(shù)");
          }

          我們來寫個簡單的 Controller 測試一下該異常,通過 POST 請求方式接收兩個參數(shù):姓名和密碼。

          @RestController
          @RequestMapping("/exception")
          public?class?ExceptionController?{

          ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(ExceptionController.class);

          ????@PostMapping("/test")
          ????public?JsonResult?test(@RequestParam("name")?String?name,
          ???????????????????????????@RequestParam("pass")?String?pass)?
          {
          ????????logger.info("name:{}",?name);
          ????????logger.info("pass:{}",?pass);
          ????????return?new?JsonResult();
          ????}
          }

          然后使用 Postman 來調(diào)用一下該接口,調(diào)用的時候,只傳姓名,不傳密碼,就會拋缺少參數(shù)異常,該異常被捕獲之后,就會進入我們寫好的邏輯,給調(diào)用方返回一個友好信息,如下:

          缺失參數(shù)異常

          2.2 處理空指針異常

          空指針異常是開發(fā)中司空見慣的東西了,一般發(fā)生的地方有哪些呢?
          先來聊一聊一些注意的地方,比如在微服務(wù)中,經(jīng)常會調(diào)用其他服務(wù)獲取數(shù)據(jù),這個數(shù)據(jù)主要是 json 格式的,但是在解析 json 的過程中,可能會有空出現(xiàn),所以我們在獲取某個 jsonObject 時,再通過該 jsonObject 去獲取相關(guān)信息時,應(yīng)該要先做非空判斷。
          還有一個很常見的地方就是從數(shù)據(jù)庫中查詢的數(shù)據(jù),不管是查詢一條記錄封裝在某個對象中,還是查詢多條記錄封裝在一個 List 中,我們接下來都要去處理數(shù)據(jù),那么就有可能出現(xiàn)空指針異常,因為誰也不能保證從數(shù)據(jù)庫中查出來的東西就一定不為空,所以在使用數(shù)據(jù)時一定要先做非空判斷。
          對空指針異常的處理很簡單,和上面的邏輯一樣,將異常信息換掉即可。如下:

          @ControllerAdvice
          @ResponseBody
          public?class?GlobalExceptionHandler?{

          ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(GlobalExceptionHandler.class);

          ????/**
          ?????*?空指針異常
          ?????*?@param?ex?NullPointerException
          ?????*?@return
          ?????*/

          ????@ExceptionHandler(NullPointerException.class)
          ????@ResponseStatus(value?
          =?HttpStatus.INTERNAL_SERVER_ERROR)
          ????public?JsonResult?handleTypeMismatchException(NullPointerException?ex)?{
          ????????logger.error("空指針異常,{}",?ex.getMessage());
          ????????return?new?JsonResult("500",?"空指針異常了");
          ????}
          }

          這個我就不測試了,代碼中 ExceptionController 有個?testNullPointException?方法,模擬了一個空指針異常,我們在瀏覽器中請求一下對應(yīng)的 url 即可看到返回的信息:

          {"code":"500","msg":"空指針異常了"}

          2.3 一勞永逸?

          當(dāng)然了,異常很多,比如還有 RuntimeException,數(shù)據(jù)庫還有一些查詢或者操作異常等等。由于 Exception 異常是父類,所有異常都會繼承該異常,所以我們可以直接攔截 Exception 異常,一勞永逸:

          @ControllerAdvice
          @ResponseBody
          public?class?GlobalExceptionHandler?{

          ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(GlobalExceptionHandler.class);
          ????/**
          ?????*?系統(tǒng)異常?預(yù)期以外異常
          ?????*?@param?ex
          ?????*?@return
          ?????*/

          ????@ExceptionHandler(Exception.class)
          ????@ResponseStatus(value?
          =?HttpStatus.INTERNAL_SERVER_ERROR)
          ????public?JsonResult?handleUnexpectedServer(Exception?ex)?{
          ????????logger.error("系統(tǒng)異常:",?ex);
          ????????return?new?JsonResult("500",?"系統(tǒng)發(fā)生異常,請聯(lián)系管理員");
          ????}
          }

          但是項目中,我們一般都會比較詳細(xì)的去攔截一些常見異常,攔截 Exception 雖然可以一勞永逸,但是不利于我們?nèi)ヅ挪榛蛘叨ㄎ粏栴}。實際項目中,可以把攔截 Exception 異常寫在 GlobalExceptionHandler 最下面,如果都沒有找到,最后再攔截一下 Exception 異常,保證輸出信息友好。

          3. 攔截自定義異常

          在實際項目中,除了攔截一些系統(tǒng)異常外,在某些業(yè)務(wù)上,我們需要自定義一些業(yè)務(wù)異常,比如在微服務(wù)中,服務(wù)之間的相互調(diào)用很平凡,很常見。要處理一個服務(wù)的調(diào)用時,那么可能會調(diào)用失敗或者調(diào)用超時等等,此時我們需要自定義一個異常,當(dāng)調(diào)用失敗時拋出該異常,給 GlobalExceptionHandler 去捕獲。

          3.1 定義異常信息

          由于在業(yè)務(wù)中,有很多異常,針對不同的業(yè)務(wù),可能給出的提示信息不同,所以為了方便項目異常信息管理,我們一般會定義一個異常信息枚舉類。比如:

          /**
          ?*?業(yè)務(wù)異常提示信息枚舉類
          ?*?@author?shengwu?ni
          ?*/

          public?enum?BusinessMsgEnum?{
          ????/**?參數(shù)異常?*/
          ????PARMETER_EXCEPTION("102",?"參數(shù)異常!"),
          ????/**?等待超時?*/
          ????SERVICE_TIME_OUT("103",?"服務(wù)調(diào)用超時!"),
          ????/**?參數(shù)過大?*/
          ????PARMETER_BIG_EXCEPTION("102",?"輸入的圖片數(shù)量不能超過50張!"),
          ????/**?500?:?一勞永逸的提示也可以在這定義?*/
          ????UNEXPECTED_EXCEPTION("500",?"系統(tǒng)發(fā)生異常,請聯(lián)系管理員!");
          ????//?還可以定義更多的業(yè)務(wù)異常

          ????/**
          ?????*?消息碼
          ?????*/

          ????private?String?code;
          ????/**
          ?????*?消息內(nèi)容
          ?????*/

          ????private?String?msg;

          ????private?BusinessMsgEnum(String?code,?String?msg)?{
          ????????this.code?=?code;
          ????????this.msg?=?msg;
          ????}
          ?//?set?get方法
          }

          3.2 攔截自定義異常

          然后我們可以定義一個業(yè)務(wù)異常,當(dāng)出現(xiàn)業(yè)務(wù)異常時,我們就拋這個自定義的業(yè)務(wù)異常即可。比如我們定義一個 BusinessErrorException 異常,如下:

          /**
          ?*?自定義業(yè)務(wù)異常
          ?*?@author?shengwu?ni
          ?*/

          public?class?BusinessErrorException?extends?RuntimeException?{
          ????
          ????private?static?final?long?serialVersionUID?=?-7480022450501760611L;

          ????/**
          ?????*?異常碼
          ?????*/

          ????private?String?code;
          ????/**
          ?????*?異常提示信息
          ?????*/

          ????private?String?message;

          ????public?BusinessErrorException(BusinessMsgEnum?businessMsgEnum)?{
          ????????this.code?=?businessMsgEnum.code();
          ????????this.message?=?businessMsgEnum.msg();
          ????}
          ?//?get?set方法
          }

          在構(gòu)造方法中,傳入我們上面自定義的異常枚舉類,所以在項目中,如果有新的異常信息需要添加,我們直接在枚舉類中添加即可,很方便,做到統(tǒng)一維護,然后再攔截該異常時獲取即可。

          @ControllerAdvice
          @ResponseBody
          public?class?GlobalExceptionHandler?{

          ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(GlobalExceptionHandler.class);
          ????/**
          ?????*?攔截業(yè)務(wù)異常,返回業(yè)務(wù)異常信息
          ?????*?@param?ex
          ?????*?@return
          ?????*/

          ????@ExceptionHandler(BusinessErrorException.class)
          ????@ResponseStatus(value?
          =?HttpStatus.INTERNAL_SERVER_ERROR)
          ????public?JsonResult?handleBusinessError(BusinessErrorException?ex)?{
          ????????String?code?=?ex.getCode();
          ????????String?message?=?ex.getMessage();
          ????????return?new?JsonResult(code,?message);
          ????}
          }

          在業(yè)務(wù)代碼中,我們可以直接模擬一下拋出業(yè)務(wù)異常,測試一下:

          @RestController
          @RequestMapping("/exception")
          public?class?ExceptionController?{

          ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(ExceptionController.class);

          ????@GetMapping("/business")
          ????public?JsonResult?testException()?{
          ????????try?{
          ????????????int?i?=?1?/?0;
          ????????}?catch?(Exception?e)?{
          ????????????throw?new?BusinessErrorException(BusinessMsgEnum.UNEXPECTED_EXCEPTION);
          ????????}
          ????????return?new?JsonResult();
          ????}
          }

          運行一下項目,測試一下,返回 json 如下,說明我們自定義的業(yè)務(wù)異常捕獲成功:

          {"code":"500","msg":"系統(tǒng)發(fā)生異常,請聯(lián)系管理員!"}

          4. 總結(jié)

          本節(jié)課程主要講解了Spring Boot 的全局異常處理,包括異常信息的封裝、異常信息的捕獲和處理,以及在實際項目中,我們用到的自定義異常枚舉類和業(yè)務(wù)異常的捕獲與處理,在項目中運用的非常廣泛,基本上每個項目中都需要做全局異常處理。

          該文已收錄到我寫的《10萬字Springboot經(jīng)典學(xué)習(xí)筆記》中,點擊下面小卡片,進入【Java開發(fā)寶典】,回復(fù):筆記,即可免費獲取。



          點贊是最大的支持?

          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  岛国av不卡 | 国产毛片毛片毛片操逼视频 | 精品人妻午夜一区二区三区四区 | 亚洲黄色一级电影 | 99热6在线观看 |