深入理解Spring異常處理

宜信技術學院

1.前言

相信我們每個人在SpringMVC開發中,都遇到這樣的問題:當我們的代碼正常運行時,返回的數據是我們預期格式,比如json或xml形式,但是一旦出現了異常(比如:NPE或者數組越界等等),返回的內容確實服務端的異常堆棧信息,從而導致返回的數據不能使客戶端正常解析; 很顯然,這些并不是我們希望的結果。

我們知道,一個較為常見的系統,會涉及控制層,服務(業務)層、緩存層、存儲層以及接口調用等,其中每一個環節都不可避免的會遇到各種不可預知的異常需要處理。如果每個步驟都單獨try..catch會使系統顯的很雜亂,可讀性差,維護成本高;常見的方式就是,實現統一的異常處理,從而將各類異常從各個模塊中解耦出來;

2.常見全局異常處理

在Spring中常見的全局異常處理,主要有三種:

(1)注解ExceptionHandler

(2)繼承HandlerExceptionResolver接口

(3)注解ControllerAdvice

在后面的講解中,主要以HTTP錯誤碼:400(請求無效)和500(內部服務器錯誤)為例,先看一下測試代碼以及沒有任何處理的返回結果,如下:

圖1:測試代碼

圖2:沒有異常的錯誤返回

2.1注解ExceptionHandler

注解ExceptionHandler作用對象為方法,最簡單的使用方法就是放在controller文件中,詳細的注解定義不再介紹。如果項目中有多個controller文件,通常可以在baseController中實現ExceptionHandler的異常處理,而各個contoller繼承basecontroller從而達到統一異常處理的目的。因為比較常見,簡單代碼如下:

圖3:Controller中的ExceptionHandler使用

在返回異常時,添加了所屬的類名,便于大家記憶理解。運行看一下結果:

圖4:添加ExceptionHandler之后的結果

  • 優點:ExceptionHandler簡單易懂,并且對于異常處理沒有限定方法格式;

  • 缺點:由于ExceptionHandler僅作用于方法,對于多個controller的情況,僅為了一個方法,所有需要異常處理的controller都繼承這個類,明明不相關的東西,強行給他們找個爹,不太好。

2.2注解ControllerAdvice

這里雖說是ControllerAdvice注解,其實是其與ExceptionHandler的組合使用。在上文中可以看到,單獨使用@ExceptionHandler時,其必須在一個Controller中,然而當其與ControllerAdvice組合使用時就完全沒有了這個限制。換句話說,二者的組合達到的全局的異常捕獲處理。

圖5:注解ControllerAdvice異常處理代碼

在運行之前,需將之前Controller中的ExceptionHandler注釋掉,測試結果如下:

圖6:注解ControllerAdvice異常處理結果

通過上面結果可以看到,異常處理確實已經變更為ExceptionHandlerAdvice類。這種方法將所有的異常處理整合到一處,去除了Controller中的繼承關系,并且達到了全局捕獲的效果,推薦使用此類方式;

2.3實現HandlerExceptionResolver接口

HandlerExceptionResolver本身SpringMVC內部的接口,其內部只有resolveException一個方法,通過實現該接口我們可以達到全局異常處理的目的。

圖7:實現HandlerExceptionResolver接口 同樣在執行之前,將上述兩個方法的異常處理都注釋掉,運行結果如下:

圖8:實現HandlerExceptionResolver接口運行結果

可以看到500的異常處理已經生效了,但是400的異常處理卻沒有生效,并且根沒有異常前的返回結果一樣。這是怎么回事呢?不是說可以做到全局異常處理的么?沒辦法要想知道問題的原因,我們只能刨根問底,往Spring的祖墳上刨,下面我們結合Spring的源碼調試,去需要原因。

3.Spring中異常處理源碼分析

大家都知道,在Spring中第一個收到請求的類就是DispatcherServlet,而該類中核心的方法就是doDispatch,我們可以在該類中打斷點,進而一步步跟進異常處理。

3.1 HandlerExceptionResolver實現類處理流程

參照如下的跟進步驟,在processHandlerException中斷點,跟蹤的結果如下圖:

圖9:processHandlerException斷點

可以看到在圖中箭頭【1】處,在遍歷 handlerExceptionResolvers 進而來處理異常,而在箭頭【2】處,看到handlerExceptionResolvers 中共有4個元素,其中最后一個就是2.3方法定義的異常處理類

當前的請求query請求,根據上述現象可以推測出,該異常處理應該是在前3個異常處理中被處理了,從而跳過我們自定義的異常;帶著這樣的猜測,我們F8繼續跟進,可以跟蹤到該異常是被第三個,即DefaultHandlerExceptionResolver所處理。

  • DefaultHandlerExceptionResolver :SpringMVC默認裝配了DefaultHandlerExceptionResolver,該類的doResolveException方法中主要對一些特殊的異常進行處理,并將這類異常轉換為相應的響應狀態碼。而query請求觸發的異常為MissingServletRequestParameterException,其恰好也是被DefaultHandlerExceptionResolver所針對的異常,故會在該類中被異常捕獲。

到此真相大白了,可以看到我們的自定義類MyHandlerExceptionResolver確實可以做到全局處理異常,只不過對于query請求的異常,中間被DefaultHandlerExceptionResolver插了一腳,所以就跳過了MyHandlerExceptionResolver類的處理,從而出現400的返回結果。而對于calc請求,中間沒有阻攔,所以就達到了預期效果。

3.2三類異常的處理順序

到此我們一共介紹了3類全局異常處理,按照上面的分析可以看出,實現HandlerExceptionResolver接口的方式是排在最后處理,那么@ExceptionHandler和@ControllerAdvice這兩個的順序誰先誰后呢? 將三類異常處理全部打開(之前注釋掉了),運行一下看看效果:

圖10:異常處理全放開運行結果

通過現象可以看到,Controller中單獨@ExceptionHandle異常處理排在了首位,@ControllerAdvice排在了第二位。嚴謹的童鞋可以寫個Controller02,將query和calc復制過去,異常處理就不要了,這樣請求c02的方法時,異常捕獲的所屬類名就都是@ControllerAdvice所在類了。

以上都是我們根據現象得到的結論,下面去Spring源碼去找“證據”。在圖9中,handlerExceptionResolvers中有4類處理器,而@ExceptionHandler和@ControllerAdvice的處理就在第一個ExceptionHandlerExceptionResolver中(之前斷點跟進即可獲知)。繼續跟進直到進入ExceptionHandlerExceptionResolver類的doResolveHandlerMethodException方法,這里的HandlerMethod就是Spring將HTTP請求映射到指定Controller中的方法,而Exception就是需要被捕獲的異常;繼續跟進,看看使用這兩個參數到底干了什么事兒。

圖11:doResolveHandlerMethodException斷點

繼續跟進getExceptionHandlerMethod方法,發現有兩個變量可能就是問題的關鍵:exceptionHandlerCache和exceptionHandlerAdviceCache。首先,兩者的變量名很值得懷疑;其次,前者在代碼中看,明顯是通過類作為key,從而得到一個處理器(resolver),這恰好Controller中@ExceptionHandler處理規則相吻合;最后,這兩個Cache的處理順序,也符合之前的得到的結論。正如之前猜測的那樣,Spring中確實是優先根據Controller類名去查找對應的ExceptionHandler,沒有找到的話,再進行@ControllerAdvice異常處理。

圖12:兩個異常處理Cache

如有興趣可繼續深入挖掘Spring的源碼,這里針對 ExceptionHandlerExceptionResolver 簡單做個總結:

  • exceptionHandlerCache中包含Controller中的ExceptionHandler異常處理,處理時通過HandlerMethod得到Controller,進而再找到異常處理方法,需要注意的是,其是在異常處理過程中put值的;

  • exceptionHandlerAdviceCache則是在項目啟動時初始化的,大概思路是找到帶有@ControllerAdvice注解的bean,從而緩存bean中的ExceptionHandler,在異常處理時需要對齊遍歷查找處理,進而達到全局處理的目的。

3.3咸魚翻身

介紹了這么多,簡單畫張圖總結一下。藍色的部分是Spring默認添加的3類異常處理器,黃色部分是我們添加的異常處理以及其所被調用的位置和順序。看看哪里還有不太清楚的,往回翻翻看(ResponseStatusExceptionResolver是針對@ResponseStatus注解,這里不再詳述)。

圖13:異常總結

如果有需要將MyHandlerExceptionResolver提前處理,甚至排在ExceptionHandlerExceptionResolver之前,能做到么?答案是肯定的,在Spring中如果想將MyHandlerExceptionResolver異常處理提前,需要再實現一個Ordered接口,實現里面的getOrder方法即可,這里返回-1,將其放在最上面,這次咸魚終于可以翻身了。

圖14:實現Ordered接口

運行看一下結果是不是符合預期,提醒一下,我們三個異常處理都是生效的,如下圖:

圖15:實現Ordered接口運行結果

4.總結

本文主要通過介紹SpringMVC中三類常見的全局異常處理,在調試中發現了問題,進而引發去Spring源碼中去探究原因,最終解決問題,希望大家能有所收獲。當然Spring異常處理類不止介紹的這些,有興趣的童鞋請自行探索!

參考鏈接:

[1] http://www.cnblogs.com/fangjian0423/p/springMVC-request-mapping.html

[2]blog.csdn.net/mll999888/a…

宜信技術學院


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

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

相關文章

基于React開發范式的思考:寫在Lesx發布之際

例子:lesx-example webpack loader: lesx-loader 一些背景 現在前端框架已經呈現出React、Angular、Vue三足鼎立的局勢,對于三者的對比以及技術選型的思考與爭論也被討論了非常多,比如知乎上的這個問題:react.js,angular.js,vue.j…

mac共享單個磁盤_如何與您的所有設備共享酒店的單個Wi-Fi連接

mac共享單個磁盤Many hotels still limit you to one or two Wi-Fi devices per room–a frustrating limitation, especially when traveling with someone else. Connection restrictions can apply anywhere you have to log into a Wi-Fi network via a portal instead of …

Python FastApi:快速建立docker容器/掛載共享文件夾/導入導出

一、目的 a.快速把原有fastapi代碼部署到docker,讓docker在server運行。 b.不涉及docker深入設置。 c.使用python第三方lib少或簡單。 二、步驟 ps:請提前安裝docker 1.新建Dockerfile,放入到項目根目錄 a.Dockerfile沒有后綴. b.準備好requireme…

PHP-FPM 與 Nginx 的通信機制總結

PHP-FPM 介紹 CGI 協議與 FastCGI 協議 每種動態語言( PHP,Python 等)的代碼文件需要通過對應的解析器才能被服務器識別,而 CGI 協議就是用來使解釋器與服務器可以互相通信。PHP 文件在服務器上的解析需要用到 PHP 解釋器,再加上對…

Android——監聽事件總結

各種監聽事件 1.按鈕 Button(1)點擊監聽btn_1.setOnClickListener(new View.OnClickListener() { (2)長按監聽btn_1.setOnLongClickListener(new View.OnLongClickListener() { 2.單選框 RadioGroupradio_gp.setOnCheckedChangeLi…

ChatGPT 大智近妖,從宇宙人生到手搓光刻機,從哄女朋友到寫年終總結我們聊得非常開心,反而讓人越來越憂心...

都說 ChatGPT 要干掉程序員,清理搜索引擎,取代Stack Overflow,還能消滅人類,這些有些言過其實了。ChatGPT 的定位是一個人工智能助理,它說,它的主要目的是通過回答用戶的問題,為用戶提供幫助。在…

如何在Windows Defender中安排掃描

Windows Defender automatically performs background scans during your PC’s idle moments, but doesn’t include an easy way to schedule a full scan. There is a way to do it, though. Windows Defender在PC空閑時自動執行后臺掃描,但是沒有包括安排完整掃…

復習深入筆記02:魔法方法/cookie,session,token/異常

魔法方法 對象生成 1.先調用__new__方法,生成空對象。控制對象生成。 2.當執行“對象類名(namelqz)”,觸發類的__init__()

比特熊故事匯獨家 | .NET 感恩專場

點擊上方藍字關注我們(本文閱讀時間:15分鐘)大家好!我是愛吃、愛玩、更愛學習技術,IT界新晉小紅人,開發者的好朋友——比特熊!比特熊:本期故事匯是.NET專場,今天一次性邀請到DOTNET領…

Ubuntu Core 給物聯網提供更多安全支持

開發四年只會寫業務代碼,分布式高并發都不會還做程序員? Canonical 是 Ubuntu 的一個桌面環境,該公司目前在云服務業務賺到了錢。因為 Ubuntu Core 為嵌入式設備帶來了 Ubuntu 18.04 長期支持(LTS)代碼庫。Ubuntu Core 的鏡像大小為 260MB&…

semantic ui要裝什么才能使用

作者:呆呆笨笨鏈接:https://www.zhihu.com/question/32233356/answer/196799506來源:知乎著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。本答案將以兩種方式講解如何從零開始使用 Semantic-UI,…

用戶帳戶控制設置_創建快捷方式以避免用戶帳戶控制彈出式快捷方式

用戶帳戶控制設置There are numerous applications which, when launched, result in a UAC (User Account Control) warning being displayed. There are reasons why this security measure is a good idea, but it can also be extremely irritating. ElevatedShortcut lets…

Java Observer Pattern(觀察者模式)

當對象間存在一對多關系時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬于行為型模式。 關鍵代碼:在抽象類里有一個 ArrayList 存放觀察者們。 優…

rest_framework01:前后端分離\規范\簡單例子(查詢某本書)

web 開發模式 RESTful規范 1 數據的安全保障 url鏈接一般都采用https協議進行傳輸 注:采用https協議,可以提高數據交互過程中的安全性 2 接口特征表現 用api關鍵字標識接口url: https://api.baidu.comhttps://www.baidu.com/api注&#xff…

.NET Core如何通過SSL訪問MongoDB?

【.NET Core】| 總結/Edison Zhou大家好,我是Edison。最近有一個ASP.NET Core通過SSL證書訪問MongoDB的需求,但是在網上發現資料很少,于是調查了一番,做了如下的筆記,希望對你有用。背景在實際場景中,開發環…

SQA

一、長大一條龍SQA計劃 SQA計劃需要包含軟件工程質量保證、質量控制、數據收集和統計報告這四方面內容,計劃以時間為線索,小組內成員為對象,以下為我組的工作計劃: 時間 任務 需完成的情況 2017年10月 用戶登錄注冊后臺實現 …

在pom.xml中配置nexus上傳地址

2019獨角獸企業重金招聘Python工程師標準>>> <distributionManagement> <repository> <id>thirdparty</id> <url>http://&#xff5b;nexusIP地址&#xff5d;:8081/nexus/content/repositories/thi…

網頁背景平鋪_在大約十秒鐘內為網頁創建無縫平鋪背景

網頁背景平鋪Creating a background image for your webpage (or desktop background) isn’t challenging at all. In fact, even a newbie Photoshop user can bash one out in about ten seconds. Here’s the simplest of simple methods with surprising, great results. …

9月11日學習內容整理:正則表達式,re模塊

一、正則表達式&#xff1a;正則是很大的一個知識點&#xff0c;不會僅僅是下面這些東西 1、概念&#xff1a;正則表達式就是一種對字符串匹配的規則&#xff0c;注意是只對字符串&#xff0c;正則表達式和python沒啥關系&#xff0c; 2、表達式&#xff1a; &#xff08;1&…

rest_framework02:修改數據/校驗鉤子/read_only和write_only

修改數據 1.傳入數據&#xff0c;選中data&#xff0c;以及修改data book_ser BookSerializer(instancebook, datarequest.data) 2.校驗&#xff0c;通過則保存。 if book_ser.is_valid(): # 返回True 表示驗證通過book_ser.save() # 不是book.save() rest_framework…