<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 中的全局異常處理

          共 7350字,需瀏覽 15分鐘

           ·

          2021-02-10 16:43

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

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

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

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

          前端或者其他服務(wù)請(qǐng)求本服務(wù)的接口時(shí),該接口需要返回對(duì)應(yīng)的 json 數(shù)據(jù),一般該服務(wù)只需要返回請(qǐng)求著需要的參數(shù)即可,但是在實(shí)際項(xiàng)目中,我們需要封裝更多的信息,比如狀態(tài)碼 code、相關(guān)信息 msg 等等,這一方面是在項(xiàng)目中可以有個(gè)統(tǒng)一的返回結(jié)構(gòu),整個(gè)項(xiàng)目組都適用,另一方面是方便結(jié)合全局異常處理信息,因?yàn)楫惓L幚硇畔⒅幸话阄覀冃枰褷顟B(tài)碼和異常內(nèi)容反饋給調(diào)用方。
          這個(gè)統(tǒng)一的 json 結(jié)構(gòu)這可以參考第02課:Spring Boot 返回 JSON 數(shù)據(jù)及數(shù)據(jù)封裝中封裝的統(tǒng)一 json 結(jié)構(gòu),本節(jié)內(nèi)容我們簡(jiǎn)化一下,只保留狀態(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)異常

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

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

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

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

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

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

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

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

          我們來寫個(gè)簡(jiǎn)單的 Controller 測(cè)試一下該異常,通過 POST 請(qǐng)求方式接收兩個(gè)參數(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í)候,只傳姓名,不傳密碼,就會(huì)拋缺少參數(shù)異常,該異常被捕獲之后,就會(huì)進(jìn)入我們寫好的邏輯,給調(diào)用方返回一個(gè)友好信息,如下:

          缺失參數(shù)異常

          2.2 處理空指針異常

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

          @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",?"空指針異常了");
          ????}
          }

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

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

          2.3 一勞永逸?

          當(dāng)然了,異常很多,比如還有 RuntimeException,數(shù)據(jù)庫還有一些查詢或者操作異常等等。由于 Exception 異常是父類,所有異常都會(huì)繼承該異常,所以我們可以直接攔截 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ā)生異常,請(qǐng)聯(lián)系管理員");
          ????}
          }

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

          3. 攔截自定義異常

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

          3.1 定義異常信息

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

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

          public?enum?BusinessMsgEnum?{
          ????/**?參數(shù)異常?*/
          ????PARMETER_EXCEPTION("102",?"參數(shù)異常!"),
          ????/**?等待超時(shí)?*/
          ????SERVICE_TIME_OUT("103",?"服務(wù)調(diào)用超時(shí)!"),
          ????/**?參數(shù)過大?*/
          ????PARMETER_BIG_EXCEPTION("102",?"輸入的圖片數(shù)量不能超過50張!"),
          ????/**?500?:?一勞永逸的提示也可以在這定義?*/
          ????UNEXPECTED_EXCEPTION("500",?"系統(tǒng)發(fā)生異常,請(qǐng)聯(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 攔截自定義異常

          然后我們可以定義一個(gè)業(yè)務(wù)異常,當(dāng)出現(xiàn)業(yè)務(wù)異常時(shí),我們就拋這個(gè)自定義的業(yè)務(wù)異常即可。比如我們定義一個(gè) 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)造方法中,傳入我們上面自定義的異常枚舉類,所以在項(xiàng)目中,如果有新的異常信息需要添加,我們直接在枚舉類中添加即可,很方便,做到統(tǒng)一維護(hù),然后再攔截該異常時(shí)獲取即可。

          @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ù)異常,測(cè)試一下:

          @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();
          ????}
          }

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

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

          4. 總結(jié)

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

          該文已收錄到我寫的《10萬字Springboot經(jīng)典學(xué)習(xí)筆記》中,點(diǎn)擊下面小卡片,進(jìn)入【武哥聊編程】,回復(fù):筆記,即可免費(fèi)獲取。


          點(diǎn)贊是最大的支持?

          瀏覽 24
          點(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>
                  精品久久做 | 色婷婷综合久久久中文字幕 | 亚洲三级无码视频 | 国精品无码一区二区三区在线秋菊 | 亚洲性爱小视频 |