我們在處理異常的時候嘉裤,時刻需要問自己以下三個問題:
- 哪里會發(fā)生異常郑临?
- 誰來處理異常?
- 如何處理異常屑宠?
一厢洞、異常的分類
Java中的異常主要分為Error和Exception,它們都是Throwable的子類典奉,具體的區(qū)別如下:
- Error表示系統(tǒng)在運行過程中躺翻,發(fā)生了不可控的錯誤,程序無法自己處理卫玖,需要人工進行干預公你,比如重啟、加內存假瞬、更換服務器等陕靠。主要的代表有
StackOverflowError
、OutOfMemoryError
脱茉,它們不需要也不允許被程序捕獲和處理剪芥,一旦發(fā)生,系統(tǒng)立即崩潰芦劣。 - Exception又可以分為兩類。
- 編譯型異常说榆,又稱檢查型異常虚吟。在程序進行編譯的時候就會檢查出來的異常,通常IDE都會提示開發(fā)人員進行處理签财。主要代表有
ClassNotFoundException
串慰、IOException
、SQLException
等唱蒸,它們都需要開發(fā)人員在程序中顯示地對它們進行處理邦鲫。 - 運行時異常,它們都是RunTimeException的子類,編譯時不會被檢查出來庆捺,等到程序運行時才會被發(fā)現(xiàn)的異常古今。主要代表有
NullPointerException
、ClassCastException
滔以、OutOfBoundsException
等捉腥。
- 編譯型異常说榆,又稱檢查型異常虚吟。在程序進行編譯的時候就會檢查出來的異常,通常IDE都會提示開發(fā)人員進行處理签财。主要代表有
下面是一張關于異常的繼承關系圖,可以幫助我們更好地理解它們之間的關系:
二你画、抓取異常
2.1 try-catch-finally
一個常見的抓取異常代碼模板如下:
try {
// 可能會拋出異常的邏輯
} catch (Exception e) {
e.printStackTrace();
} finally {
// 資源回收操作
}
2.2 try-with-resource
這是一個語法糖抵碟,其實就是自動調用資源的close方法,省去開發(fā)人員寫finally方法坏匪,大致的代碼模板如下:
try (/*資源的初始化*/) {
// 可能會拋出異常的邏輯
} catch (Exception e) {
e.printStackTrace();
}
此處只是簡單說明拟逮,具體而詳細的使用已經(jīng)在另一篇文章《如何在try代碼塊中合理地關閉資源》中闡述了。
三适滓、處理異常
3.1 DAO層
與數(shù)據(jù)庫交互的DAO層往往會拋出各種各樣的異常敦迄,通常不需要為每一種異常都建立對應的自定義業(yè)務異常,會顯得很繁雜粒竖。
我們應該將DAO層的異常統(tǒng)一封裝為DAOException然后再向上拋出颅崩,然后在業(yè)務層接受這些異常的時候,可以把異常堆棧打印出來蕊苗,如此也能清晰地定位到具體的原因沿后,比如下面的示例:
public Long generateOrderId(Long userId) throws DAOException {
try {
...
} catch (Exception e) {
throw new DAOException("generateOrderId error, userId=" + userId, e);
}
}
以上代碼簡單地描述了異常的信息,并給出了出錯的userId朽砰,還把錯誤堆棧也一塊向上throw尖滚,這才是標準的易于定位的異常處理方式。
然后瞧柔,在業(yè)務層捕獲異常的時候需要開發(fā)者自己判斷漆弄,當前的異常是否在該方法的處理能力范圍之內?如果是造锅,那就可以自己默默把異常的情況處理掉撼唾,如果不是诡蜓,則需要繼續(xù)向外拋出览爵,給能處理這個異常的調用方去處理础米。
嚴禁捕獲異常后什么也不處理或者只打印一行日志暂幼。因為不處理的話會導致當前請求處理過程中斷厂榛,很可能直接就將異常堆棧返回給給調用者号枕,體驗極差慕趴。而打印一行日志而沒有堆棧信息的話碴卧,則完全無法定位異常發(fā)生的根本原因是什么深夯,大大增加了排查的難度抖格。
所以,如果是需要向上拋出的異常,一定要在異常對象中添加上下文參數(shù)雹拄、局部變量收奔、環(huán)境信息等,不可只是簡單地一句話了事办桨,會增加問題的排查難度筹淫。
3.2 Service層
Service層通常承擔著對調用方返回數(shù)據(jù)的責任,返回什么格式的數(shù)據(jù)(異常)在不同的場景下是不同的呢撞,通常有以下的建議:
- 對外提供的開放接口使用錯誤碼的返回方式损姜;
- 公司內部跨應用的遠程服務調用使用Result對象來封裝錯誤碼和具體的錯誤信息;
- 應用內部則直接拋出異常對象即可殊霞。
通常摧阅,我們在業(yè)務層都會拋出表示特定業(yè)務邏輯出錯的自定義異常,比如“賬號密碼驗證錯誤異潮炼祝”棒卷、“查詢條件為空異常”等祝钢。這樣做的好處是比规,一目了然就知道程序發(fā)生異常的業(yè)務原因是什么。
但是拦英,為了避免自定義異常的泛濫蜒什,可以優(yōu)先使用業(yè)界或者團隊內已經(jīng)定義過的異常,其次再使用根據(jù)業(yè)務場景自定義的異常類疤估,不建議使用Java中原生的異常類直接進行異常的拋出灾常。
最后要說的是,調用方和服務方往往會因為誰來處理異常這件事吵得不可開交铃拇,按照業(yè)界的開發(fā)規(guī)范钞瀑,異常的處理通常都是調用方需要處理的,服務方可以不處理慷荔,但必須要有所說明雕什,提示調用方。