本文介紹本文僅按照業(yè)務(wù)系統(tǒng)開發(fā)角度描述異常的一些處理看法.不涉及java的異?;A(chǔ)知識,可以自行查閱 《Java核心技術(shù) 卷I》 和 《java編程思想》 可以得到更多的基礎(chǔ)信息. 寫在前面的話筆者文筆功力尚淺,言語多有不妥,請慷慨指正,必定感激不盡. 本文提出了幾個概念: 處理反饋 業(yè)務(wù)異常 代碼錯誤 ,請認(rèn)真思考一下各中區(qū)別. 在開發(fā)業(yè)務(wù)系統(tǒng)中,我們目前絕大多數(shù)采用MVC模式,但是往往有人把service跟controller緊緊的耦合在一起,甚至直接使用Threadlocal來隱式傳值,并且復(fù)雜的邏輯幾乎只能使用service中存儲的全局對象來傳遞處理結(jié)果,包括異常. 這樣一來首先有違MVC模式,二來邏輯十分不清晰,難以維護.本文結(jié)合工作經(jīng)驗,給出一些異常使用建議,使用spring來實戰(zhàn)異常為我們帶來的好處. 常常,我們讀罷了各種java的書,異常的各種機制,特性都很清楚,但是始終還是不知道如何使用,甚至背下了概念,卻不知道如何致用. 我們開發(fā)的業(yè)務(wù)系統(tǒng),或者是產(chǎn)品,常常面臨著這樣的問題:
什么情況需要自定義異常經(jīng)??吹揭恍╉椖?在全局定義一個 AppException,然后所有地方都只拋出這個異常,并且把捕獲的異常case到這個AppException中.會有如下問題:
什么情況需要手動處理異常我不會把書上的東西直接復(fù)制下來,這里說一下容易記住的,并且適合業(yè)務(wù)開發(fā)的.
自定義業(yè)務(wù)異常考慮如下場景: 系統(tǒng)提供一個API,用于修改用戶信息,服務(wù)器端采用json數(shù)據(jù)交互.首先我們定義ServiceException,用來表示業(yè)務(wù)邏輯受理失敗,它僅表示我們處理業(yè)務(wù)的時候發(fā)現(xiàn)無法繼續(xù)執(zhí)行下去.
接下來看下Controller層.
關(guān)于上述Controller寫法乍一看會有一些冗余,如果無法理解,請仔細研讀MVC設(shè)計模式. 先不管service,我們來考慮下. 一個業(yè)務(wù)系統(tǒng)不可能不對用戶提交的數(shù)據(jù)進行驗證,驗證包括兩方面 : 有效性和合法性,
有效性檢查,可以交給java的校驗框架執(zhí)行,比如JSR303. 假設(shè)用戶提交的數(shù)據(jù)經(jīng)過驗證都合法,還是有一些情況是不能調(diào)用修改邏輯的.
對于前3種,我們認(rèn)為是有效性檢查失敗,第4種屬與我們無法處理的異常,第5種就是程序員bug. 現(xiàn)在的問題是,前三種情況我們?nèi)绾瓮ㄖ脩裟?
顯然前2種方法都不可取 ,因為MVC不設(shè)計模式告訴我們,controller是用來接收頁面參數(shù),并且調(diào)用邏輯處理,最后組織頁面響應(yīng)的地方.我們不可以在controller進行邏輯處理,controller只應(yīng)該負(fù)責(zé)用戶API入口和響應(yīng)的處理(如若不然,思考一下如果有一天service的代碼打包成jar放到另一個平臺,沒有controller了,該怎么辦?) 狀態(tài)碼機制是個不錯的選擇,可是如此一來,用戶保存邏輯變了,比如增加一個情況,不允許修改已經(jīng)離職的用戶,那么我們還需要修改controller的代碼,代碼量增加,維護成本增高,并且還耦合了service,不符合MVC設(shè)計模式. 那么怎么辦呢?現(xiàn)在我們來看下service代碼如何編寫
這樣一來只要我們檢查到不允許保存的項目,我們就可以直接throw 一個新的異常,異常機制會幫助我們中斷代碼執(zhí)行. 接下來有2種選擇:
第1種方式是不可取的 ,注意我們拋出的ServiceException,它僅僅邏輯處理異常,并且我們的方法前面沒有聲明throws ServiceException,這表示他是一個非受查異常.controller也沒有關(guān)心會發(fā)生什么異常. 為什么不定義成受查異常呢? 如果是一個受查異常,那么意味著controller必須要處理你的異常.并且如果有一天你的業(yè)務(wù)邏輯變了,可能多一種檢查項,就需要增加一個異常,反之需要刪除一個異常,那么你的方法簽名也需要改變,controller也隨之要改變,這又變成了緊耦合,這和用狀態(tài)碼123表示處理結(jié)果沒有什么不同. 我們可以為每一種檢查項定義一個異常嗎? 可以,但是那樣顯得太多余了.因為業(yè)務(wù)邏輯處理失敗的時候,根據(jù)我們需求,我們只需要通知用戶失敗的原因(通常應(yīng)該是一段字符串),以及服務(wù)器受理失敗的一個狀態(tài)碼(有時可能不需要狀態(tài)碼,這要看你的設(shè)計了),這樣這需要一個包含原因?qū)傩缘漠惓<纯蓾M足我們需求. 最后我們決定這個異常繼承自RuntimeException.并且包含一個接受一個錯誤原因的構(gòu)造器,這樣controller層也不需要知道異常,只要全局捕獲到ServiceException做統(tǒng)一的處理即可,這無論是在struct1,2時代,還是springMVC中,甚至servlet年代,都是極為容易的! 異常不提供無參構(gòu)造器 ,因為絕對不允許你拋出一個邏輯處理異常,但是不指明原因,想想看,你是必須要告訴用戶為什么受理失敗的! 如此一來,我們只需要全局統(tǒng)一處理下 ServiceException 就可以了,很好,spring為我們提供了ControllerAdvice機制,有關(guān)ControllerAdvice,可以查閱springMVC使用文檔,下面是一個簡單的示例:
在這個時候,我們就可以很輕松的處理各種情況了. 注意一點,在這個類中,我們定義了2個log對象,分別指向 ServiceException.class 和 ModuleControllerAdvice.class . 并且處理 ServiceException的時候使用了info級別的日志輸出,這是很有用的.
接下來你可以在修改用戶的時候想客戶端響應(yīng)這樣的JSON
如此一來沒有任何地方需要關(guān)心異常,或者業(yè)務(wù)邏輯校驗失敗的情況.用戶也可以得到很友好的錯誤提示. 如何對異常進行分類如果你只需要一句概括,那么直接定義一個簡單的異常,用于中斷處理,并且與用戶保持友好交互即可. 如果不可能一句話描述清楚,并且包含附加信息,比如需要在日志或者數(shù)據(jù)庫記錄消息ID,此時可能專門針對這種重要/復(fù)雜業(yè)務(wù)創(chuàng)建獨立異常. 上述兩種情況因為web系統(tǒng),是用戶發(fā)起請求之后需要等待程序給予響應(yīng)結(jié)果的. 如果是后臺作業(yè),或者復(fù)雜業(yè)務(wù)需要追溯性.這種通常用流程判斷語句控制,要用異常處理.我們認(rèn)為這些流程判斷一定在一個原子性處理中.并且檢查到(不是遇到)的問題(不是異常)需要記錄到用戶可友好查看的日志.這種情況屬于處理反饋,并不叫異常. 綜上,筆者通常分為如下幾類:
各類異常必須要有單獨的日志記錄,或者分級,分類可管理.有的時候僅僅想給三方運維看到邏輯異常. 寫在后面的注意
上面這句話出自<java編程思想>,但是我們思考如下幾點: 業(yè)務(wù)邏輯檢查,也是意外情況 UnknownHostException,表示找不到這樣的主機,這個異常和NoUserException有什么區(qū)別么?換言之,沒有這樣的主機是異常,沒有這樣的用戶不是異常了么? 所以一定要弄明白什么是用異常來控制邏輯,什么是定義程序異常. 異常處理效率很低 書中所示的例子,是在循環(huán)中大量使用try-catch進行檢查,但是業(yè)務(wù)系統(tǒng),用戶發(fā)起請求的次數(shù)與該場景天壤地別.淘寶的11`11是個很好的反例.但是請你的系統(tǒng)上到這個級別再考慮這種問題.
我們先來看一個例子:
上述代碼就是典型的使用異常來處理業(yè)務(wù)邏輯.這種方式需要嚴(yán)重的禁止!上述代碼最大的問題在于,我們?nèi)绾卫卯惓碜詣犹幚硎聞?wù)呢? 然而這和我們的異常中斷service沒有什么沖突.也并不是一回事.
改正后的邏輯
最后俏皮一句:微服務(wù)橫行的今天,我們在action里面直接寫業(yè)務(wù)處理,也無可厚非. 原文鏈接:https://my.oschina.net/c5ms/blog/1827907 |
|
|
來自: 日月桃子 > 《java異常處理》