技術派全局異常處理

前言

全局的異常處理是Java后端不可或缺的一部分,可以提高代碼的健壯性和可維護性。

在我們的開發中,總是難免會碰到一些未經處理的異常,假如沒有做全局異常處理,那么我們返回給用戶的信息應該是不友好的,很抽象的,用戶會認為我們的程序是不安全的。

相反,如果有了全局異常處理,那么我們就可以給用戶提供更友好的反饋。

我們甚至可以把全局異常處理寫到簡歷上,比如說你可以這樣描述:項目采用了 HandlerExceptionResolver(或者 ControllerAdvice 方案)的全局異常處理策略,提高了代碼的健壯性和可維護性,優化了用戶體驗。

我會結合具體的業務場景給大家一種身臨其境的感覺(@),講一講HandlerExceptionResolver 和 ControllerAdvice 具體怎么在項目中使用。

業務場景

技術派整合了Redis,比如用戶登錄的時候會從Redis中獲取緩存,那假如我們沒有啟動Redis服務呢?

然后我們在本地啟動技術派的服務端。

然后點擊登錄 ->一鍵登錄。

然后就會收到這樣一條提示信息。

由于我們項目是開源的,所以這里就直接把服務端的信息返回出來,好讓大家第一時間辨別出是哪里出了問題,可以及時去調整。

當你看到這樣一條錯誤提示,第一時間就能明白,哦,原來是 Redis 沒有啟動啊。

在服務器端的控制臺面板中(錯誤堆棧信息中),可以找到對應的錯誤信恙。

其中 ForumExceptionHandler 就是用來進行全局異常處理的,它是HandlerExceptionResolver 接囗的實現類

HandlerExceptionResolver

HandlerExceptionResolver 是 Spring 提供的一種異常處理機制,它允許我們在應用程序中以統一的方式處理控制器方法引發的異常。

要使用 HandlerExceptionResolver,我們需要創建一個實現該接口的類,并在其中定義如何處理異常。例如:

@Slf4j
@Order(-100)
public class ForumExceptionHandler implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {}

  • @Slf4j 是 lombok 提供的一個日志注解。
  • @0rder 注解用于指定 Spring 中組件的加載順序。它接受一個整數值,數值越小,組件的優先級越高,加載順序越靠前。
  • 在 resolveException 方法中,我們可以自定義異常處理邏輯,根據異常類型返回不同的 ModelAndView。

我們來看一下 resolveException 方法中的具體寫法:

@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {Status errStatus = buildToastMsg(ex);if (restResponse(request, response)) {// 表示返回json數據格式的異常提示信息if (response.isCommitted()) {// 如果返回已經提交過,直接退出即可return new ModelAndView();}try {response.reset();// 若是rest接口請求異常時,返回json格式的異常數據;而不是專門的500頁面response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.setHeader("Cache-Control", "no-cache, must-revalidate");response.getWriter().println(JsonUtil.toStr(ResVo.fail(errStatus)));response.getWriter().flush();response.getWriter().close();return new ModelAndView();} catch (Exception e) {throw new RuntimeException(e);}}String view = getErrorPage(errStatus, response);ModelAndView mv = new ModelAndView(view);response.setContentType(MediaType.TEXT_HTML_VALUE);mv.getModel().put("global", SpringUtil.getBean(GlobalInitService.class).globalAttr());mv.getModel().put("res", ResVo.fail(errStatus));mv.getModel().put("toast", JsonUtil.toStr(ResVo.fail(errStatus)));return mv;
}

技術派做了兩種處理,一種是REST接口請求的異常,一種是針對普通頁請求的異常。

1、如果是 REST 接口請求異常,代碼會返回一個 JSON 格式的異常提示信息:

  • 首先檢查響應是否已經提交,如果已經提交,則直接返回一個空的 ModelAndView。
  • 如果響應未提交,將重置響應對象,設置響應的內容類型為JSON,并添加相關的響應頭。
  • 使用 response.getwriter()將異常狀態對象 errStatus 轉換為 JSON 格式并寫入響應。完成后,返回一個空的 ModelAndView。

②、如果是普通頁面請求異常,代碼會返回一個包含錯誤信息的 HTML 頁面:

  • 根據異常狀態對象 errStatus 和響應對象 response 獲取錯誤頁面的視圖名稱。
  • 創建-個 ModelAndView 對象,并設置視圖名稱。
  • 設置響應的內容類型為 HTML。
  • 向 ModelAndView 中添加全局屬性、錯誤響應對象以及錯誤信息(以JSON 格式)
  • 最后返回這個 ModelAndView 對象,用于展示錯誤頁面。

下圖是當遇到 404 錯誤的時候,返回的 404 頁面。

其中 buildToastMsg 方法用來對異常進行分類,使用 instanceof 關鍵字來判斷不同類型的異常,添加不同的異常碼和提示消息。

private Status buildToastMsg(Exception ex) {
if (ex instanceof ForumException) {return ((ForumException) ex).getStatus();
} else if (ex instanceof AsyncRequestTimeoutException) {return Status.newStatus(StatusEnum.UNEXPECT_ERROR, "超時未登錄");
} else if (ex instanceof HttpMediaTypeNotAcceptableException) {return Status.newStatus(StatusEnum.RECORDS_NOT_EXISTS, ExceptionUtils.getStackTrace(ex));
} else if (ex instanceof HttpRequestMethodNotSupportedException || ex instanceof MethodArgumentTypeMismatchException || ex instanceof IOException) {// 請求方法不匹配return Status.newStatus(StatusEnum.ILLEGAL_ARGUMENTS, ExceptionUtils.getStackTrace(ex));
} else if (ex instanceof NestedRuntimeException) {log.error("unexpect NestedRuntimeException error! {}", ReqInfoContext.getReqInfo(), ex);return Status.newStatus(StatusEnum.UNEXPECT_ERROR, ex.getMessage());
} else {log.error("unexpect error! {}", ReqInfoContext.getReqInfo(), ex);return Status.newStatus(StatusEnum.UNEXPECT_ERROR, ExceptionUtils.getStackTrace(ex));
}
}

StatusEnum 中定義了異常碼的規范,舉幾個例子。

/*** 異常碼規范:* xxx - xxx - xxx* 業務 - 狀態 - code* <p>* 業務取值* - 100 全局* - 200 文章相關* - 300 評論相關* - 400 用戶相關* <p>* 狀態:基于http status的含義* - 4xx 調用方使用姿勢問題* - 5xx 服務內部問題* <p>* code: 具體的業務code*/
@Getter
public enum StatusEnum {SUCCESS(0, "OK"),// -------------------------------- 通用// 全局傳參異常ILLEGAL_ARGUMENTS(100_400_001, "參數異常"),ILLEGAL_ARGUMENTS_MIXED(100_400_002, "參數異常:%s"),// 全局權限相關FORBID_ERROR(100_403_001, "無權限"),
}

getErrorPage 方法用于返回不同的錯誤頁面,比如常見的 404 Not Found(請求的資源不存在,服務器無法找到請求的資源)、403 Forbidden(服務器理解請求,但是拒絕處理它,一般是由于權限問題或者訪問被拒絕)500 lnternal Server Error(服務器發生了錯誤,無法完成請求)等。

private String getErrorPage(Status status, HttpServletResponse response) {// 根據異常碼解析需要返回的錯誤頁面if (StatusEnum.is5xx(status.getCode())) {response.setStatus(500);return "error/500";} else if (StatusEnum.is403(status.getCode())) {response.setStatus(403);return "error/403";} else {response.setStatus(404);return "error/404";}
}

restResponse 方法用來判斷是否是 REST 請求,比如說 admin 后臺請求、api數據請求、上傳圖片等接口

Ajax 請求等,這些請求統一返回 JSON 格式的異常提示信息,否則返回普通的頁面格式的異常提示信息。

**
* 后臺請求、api數據請求、上傳圖片等接口,返回json格式的異常提示信息
* 其他異常,返回500的頁面
*
* @param request
* @param response
* @return
*/
private boolean restResponse(HttpServletRequest request, HttpServletResponse response) {if (request.getRequestURI().startsWith("/api/admin/") || request.getRequestURI().startsWith("/admin/")) {return true;}if (request.getRequestURI().startsWith("/image/upload")) {return true;}if (response.getContentType() != null && response.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {return true;}if (isAjaxRequest(request)) {return true;}// 數據接口請求AntPathMatcher pathMatcher = new AntPathMatcher();if (pathMatcher.match("/**/api/**", request.getRequestURI())) {return true;}return false;
}

再來看一下自定義的異常類 ForumException,非常簡單,繼承了 RuntimeException。

/*** 業務異常**/
public class ForumException extends RuntimeException {@Getterprivate Status status;public ForumException(Status status) {this.status = status;}public ForumException(int code, String msg) {this.status = Status.newStatus(code, msg);}public ForumException(StatusEnum statusEnum, Object... args) {this.status = Status.newStatus(statusEnum, args);}}

HandlerExceptionResolver 的工作原理主要基于 Spring MVC 的異常處理流程。當一個請求進入 Spring MVC后,它會根據請求信息找到對應的處理器(handler,也就是Controller)。在Controller 執行過程中,如果拋出了異常,Spring MVC 就會啟動異常處理流程。

1)異常發生:當 Controller 執行過程中拋出異常,Spring MVC 捕獲到這個異常后,會進入異常處理流程

2)查找異常解析器:Spring MVC 會遍歷所有已注冊的 HandlerExceptionResolver 實現。比如說我們自定義的

ForumExceptionHandler,Spring MVC 本身也提供了一些默認的實現,比如DefaultHandlerExceptionResolver、ExceptionHandlerExceptionResolver.

3)執行異常解析器:對于每個 HandlerExceptionResolver 實現,Spring MVC會調用它的 resolveException方法,并傳入請求、響應、處理器和異常對象。如果解析器能處理這個異常,它會返回一個非空的ModelAndView 對象。這個對象封裝了異常處理后的視圖和模型數據。

4)處理返回結果:當 resolveException 方法返回一個非空的 ModelAndView 對象時,Spring MVC 會將這個對象用于生成最終的響應。可能渲染一個錯誤視圖、設置響應狀態碼等。如果所有的HandlerExceptionResolver 都無法處理這個異常(即都返回了空的 ModelAndView對象),那么 SpringMVC 會將異常重新拋出,以便其他異常處理器(如 Servet 容器)進行處理。

通過這個流程,HandlerExceptionResolver 能夠在 Spring MVC 中統一管理和處理異常。記得在 Spring Boot的啟動類中將自定義的 HandlerExceptionResolver 添加到 Spring 配置中。

@Slf4j
@EnableAsync
@EnableScheduling
@EnableCaching
@ServletComponentScan  //與@WebFilter(urlPatterns = "/*", filterName = "reqRecordFilter", asyncSupported = true)注解配套
@SpringBootApplication
public class QuickForumApplication implements WebMvcConfigurer, ApplicationRunner {@Overridepublic void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {resolvers.add(0, new ForumExceptionHandler());}
}

@ControllerAdvice

除了 HandlerExceptionResolver,全局異常還可以采用 @ControllerAdvice 注解的方式。它可以將通用的操作和邏輯抽離出來,避免在每個控制器中重復相同的操作。

第一步,新建一個自定義的異常類 ForumAdviceException。

/*** 業務異常**/
public class ForumAdviceException extends RuntimeException {@Getterprivate Status status;public ForumAdviceException(Status status) {this.status = status;}public ForumAdviceException(int code, String msg) {this.status = Status.newStatus(code, msg);}public ForumAdviceException(StatusEnum statusEnum, Object... args) {this.status = Status.newStatus(statusEnum, args);}}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Status {/*** 業務狀態碼*/@ApiModelProperty(value = "狀態碼, 0表示成功返回,其他異常返回", required = true, example = "0")private int code;/*** 描述信息*/@ApiModelProperty(value = "正確返回時為ok,異常時為描述文案", required = true, example = "ok")private String msg;public static Status newStatus(int code, String msg) {return new Status(code, msg);}public static Status newStatus(StatusEnum status, Object... msgs) {String msg;if (msgs.length > 0) {msg = String.format(status.getMsg(), msgs);} else {msg = status.getMsg();}return newStatus(status.getCode(), msg);}
}

第二步,新建一個全局異常控制器 GlobalExceptionHandler,內容如下所示。

@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(value = ForumAdviceException.class)public ResVo<String> handleForumAdviceException(ForumAdviceException e) {return ResVo.fail(e.getStatus());}
}

@RestControllerAdvice 是一個特殊的 @ControllerAdvice 注解,適用于處理 RESTfu API 異常的情況。這意味著它將用于處理來自帶有 @RestController 注解的控制器拋出的異常。

此類中定義的方法 handleForumAdviceException 使用 @ExceptionHandler 注解,表示它將處理ForumAdviceException 類型的異常。

第三步,加一個測試的控制器方法 testControllerAdvice。

@RequestMapping(path = "testControllerAdvice")
@ResponseBody
public String testControllerAdvice() {throw new ForumAdviceException(StatusEnum.ILLEGAL_ARGUMENTS_MIXED, "測試ControllerAdvice異常");
}

第四步,如果之前在啟動類中注冊了 ForumExceptionHandler,此時需要干掉。

第五步,重啟啟動服務,測試 testControllerAdvice 接口。

接口返回的內容如下所示

兩種全局異常處理的優缺點

好,我們來對比一下兩種全局異常處理 HandlerExceptionResolver 和 @ControllerAdvice(或@RestControllerAdvice)的優缺點。

1、HandlerExceptionResolver

HandlerExceptionResolver 是一個接口,用于處理由 Controller 拋出的異常,我們可以重寫resolveException方法,在其中實現該接口來自定義全局異常處理邏輯。然后在Spring Boot 的啟動類中通過

extendHandlerExceptionResolvers 將自定義的 HandlerExceptionResolver 添加到解析器中。

  • 優點:可以更加靈活地處理異常,因為你可以編寫任何處理邏輯。
  • 缺點:與其他 Spring MVC 組件的集成不夠緊密,需要手動添加和配置,。

2、@ControllerAdvice(或@RestControllerAdvice)

@ControllerAdvice(或 @RestControllerAdvice)是用于定義全局異常處理類的注解。在這個類中,我們可以使用 @ExceptionHandler 注解來處理不同類型的異常。@ControllerAdvice 需要與 @ExceptionHandler 注解一起使用。

  • 優點:更容易實現和集成,只需創建一個帶有 @ControllerAdvice(或 @RestControllerAdvice)注解的類,并使用 @ExceptionHandler 注解定義異常處理方法。
  • 缺點:異常處理邏輯可能不如 HandlerxceptionResolver 那么靈活。

我們可以根據具體的需求和使用場景,選擇其中之一來實現全局異常處理。另外二者可以共存,

ControllerAddvice優先級比HandlerExceptionResolver高,也可以用@order注解指定優先級。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/38922.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/38922.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/38922.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【一篇文章帶你搞懂--拉鏈表!!!拉鏈表的原理是什么!】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是書生?&#xff0c;今天主要和大家分享一下拉鏈表的原理以及使用,希望對大家有所幫助。 大家可以關注我下方的鏈接更多優質文章供學習參考。 &#x1f49e;&#x1f49e;代碼是你的畫筆&#xff0c;創新是你…

深入解析:WebKit的JavaScript引擎與V8引擎的比較研究

在現代Web開發中&#xff0c;JavaScript引擎是瀏覽器的核心組件之一&#xff0c;它們負責解析和執行JavaScript代碼。WebKit和V8是兩個非常著名的JavaScript引擎&#xff0c;分別被用于不同的瀏覽器和環境中。WebKit的JavaScript引擎最初是Nitro&#xff0c;后來被JavaScriptCo…

【超簡單-Java設計模式1】設計模式的定義、分類及七大設計原則

引言 Java設計模式從入門到精通-設計模式的定義、設計模式分類及七大設計原則 設計模式簡介 在軟件開發中&#xff0c;設計模式是解決常見設計問題的最佳實踐。它們為開發者提供了一種通用的解決方案&#xff0c;使得代碼更加靈活、可復用和可維護。在Java編程語言中&#x…

Flink 運行時架構

Flink 運行時的組件 作業管理器&#xff08;JobManager&#xff09;資源管理器&#xff08;ResourceManager&#xff09;任務管理器&#xff08;TaskManager&#xff09;分發器&#xff08;Dispatch&#xff09; JobManager 控制一個應用程序執行的主進程&#xff0c;也就是說…

LiveNVR監控流媒體Onvif/RTSP用戶手冊-概覽:CPU使用、存儲使用、帶寬使用、負載、內存使用、通道統計

LiveNVR監控流媒體Onvif/RTSP用戶手冊-概覽:CPU使用、存儲使用、帶寬使用、負載、內存使用、通道統計 1、概覽1.1、通道統計1.2、負載1.3、CPU使用1.4、存儲使用1.5、帶寬使用1.6、內存使用 2、RTSP/HLS/FLV/RTMP拉流Onvif流媒體服務 1、概覽 1.1、通道統計 顯示可用通道&…

構建Kylin Cube的藝術:最佳實踐指南

構建Kylin Cube的藝術&#xff1a;最佳實踐指南 Apache Kylin是一個開源的大數據分析引擎&#xff0c;專為大規模數據集提供快速的查詢能力。Kylin的核心是Cube&#xff0c;它是一種多維數據模型&#xff0c;能夠顯著提高查詢性能。然而&#xff0c;設計一個高效的Cube需要考慮…

Lipschitz 連續,絕對連續

1. Lipschitz 連續 經常聽到這個名詞&#xff0c; Lipschitz 連續比普通連續更強&#xff0c;不僅要求函數連續&#xff0c;還要求函數的梯度小于一個正實數。 在單變量實數函數上的定義可以是&#xff1a; 對于定義域內任意兩個 x 1 x_1 x1? and x 2 x_2 x2?, 存在一個…

云計算與生成式AI的技術盛宴!亞馬遜云科技深圳 Community Day 社區活動流程搶先知道!

小李哥最近要給大家分享7月7日在深圳的即將舉辦的亞馬遜云科技生成式AI社區活動Community Day &#xff0c;干貨很多內容非常硬核&#xff0c;不僅有技術分享學習前沿AI技術&#xff0c;大家在現場還可以動手實踐沉浸式體驗大模型&#xff0c;另外參與現場活動還可以領取諸多精…

順序表(C語言詳細版)

1. 線性表 線性表(lina list)是n個具有相同特性的數據元素的有限序列。線性表是一種在實際中廣泛使用的數據結構&#xff0c;常見的線性表&#xff1a;順序表、鏈表、棧、隊列、字符串...... 線性表在邏輯上是線性結構&#xff0c;也就是說連續的一條直線。但是在物理結構上并…

一文匯總全球熱門新聞API

新聞API通過提供快速、準確和全面的新聞內容&#xff0c;已經成為現代社會不可或缺的一部分&#xff0c;對人們的生活、工作環境和科技發展產生了深遠的影響。新聞API使人們能夠快速獲取來自世界各地的實時新聞和信息&#xff0c;提高了信息的可訪問性。通過新聞API&#xff0c…

C++算法學習心得八.動態規劃算法(6)

1.最長遞增子序列&#xff08;300題&#xff09; 題目描述&#xff1a; 給你一個整數數組 nums &#xff0c;找到其中最長嚴格遞增子序列的長度。 子序列是由數組派生而來的序列&#xff0c;刪除&#xff08;或不刪除&#xff09;數組中的元素而不改變其余元素的順序。例如&…

Redis分布式集群部署

目錄 一. 原理簡述 二. 集群配置??????? 2.1 環境準備 2.2 編譯安裝一個redis 2.3 創建集群 2.4 寫入數據測試 實驗一&#xff1a; 實驗二&#xff1a; 實驗三&#xff1a; 實驗四&#xff1a; 添加節點 自動分配槽位 提升節點為master&#xff1a; 實驗…

關于電商平臺分類||電商平臺商品分類接口|電商平臺商品數據

電商平臺 做電商&#xff0c;則要有電商平臺&#xff0c;一個為 企業 或 個人 提供網上交易洽談的平臺。. 企業電子商務平臺是建立在 Internet 網上進行商務活動的虛擬網絡空間和保障商務順利運營的管理環境&#xff1b;是協調、整合 信息流 、貨物流、 資金流 有序、關聯、高效…

會員信息一鍵同步!微盟與客如云聯手打造智能服務新體驗!

客戶介紹 某房地產開發有限公司&#xff0c;自成立以來一直深耕于房地產行業&#xff0c;憑借卓越的開發實力和前瞻性的市場眼光&#xff0c;成為了業界備受矚目的企業。多年來&#xff0c;該公司始終堅持“品質至上&#xff0c;客戶為先”的經營理念&#xff0c;致力于為客戶…

新一代Java框架Quarkus的性能優化與應用

新一代Java框架Quarkus的性能優化與應用 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 引言 隨著云原生技術的發展&#xff0c;Java開發者們對于構建輕量級、…

JavaScript 編程語言【 數據類型】過濾|排序|映射|迭代

文章目錄 將 border-left-width 轉換成 borderLeftWidth過濾范圍原位&#xff08;in place&#xff09;過濾范圍降序排列復制和排序數組創建一個可擴展的 calculator映射到 names映射到對象按年齡對用戶排序隨機排列數組獲取平均年齡數組去重從數組創建鍵&#xff08;值&#x…

掌握React與TypeScript:從零開始繪制中國地圖

最近我需要使用reactts繪制一個界面&#xff0c;里面需要以中國地圖的形式展示區塊鏈從2019-2024年這五年的備案以及注銷情況&#xff0c;所以研究了一下這方面的工作&#xff0c;初步有了一些成果&#xff0c;所以現在做一些分享&#xff0c;希望對大家有幫助&#xff01; 在這…

手把手搞定報名亞馬遜科技認證

引言 亞馬遜云科技認證考試為我們這些技術從業者提供了提升專業技能的機會。無論選擇線上還是線下考試&#xff0c;每種方式都有其獨特的優勢和挑戰。選擇合適的考試方式將幫助我們更好地展示自己的技術水平。以下是我對不同考試方式的優缺點介紹&#xff0c;以及各科目的考試…

【pytorch12】什么是梯度

說明 導數偏微分梯度 梯度&#xff1a;是一個向量&#xff0c;向量的每一個軸是每一個方向上的偏微分 梯度是有方向也有大小&#xff0c;梯度的方向代表函數在當前點的一個增長的方向&#xff0c;然后這個向量的長度代表了這個點增長的速率 藍色代表比較小的值&#xff0c;紅色…

七月論文審稿GPT第5版:拿我司七月的早期paper-7方面review數據集微調LLama 3

前言 llama 3出來后&#xff0c;為了通過paper-review的數據集微調3&#xff0c;有以下各種方式 不用任何框架 工具 技術&#xff0c;直接微調原生的llama 3&#xff0c;畢竟也有8k長度了 效果不期望有多高&#xff0c;純作為baseline通過PI&#xff0c;把llama 3的8K長度擴展…