Spring MVC的異常處理

使用Spring MVC搭建一個(gè)web應(yīng)用時(shí),我們有很多種辦法處理異常并返回異常視圖給browser愧口,下面我們分別介紹幾種異常的處理方式雨让。

通過(guò)HandlerExceptionResolver處理異常

該接口是DispatcherServlet提供的唯一的異常處理機(jī)制,在Spring MVC內(nèi)部所有的異常處理方式都是基于該機(jī)制實(shí)現(xiàn)的丽涩,包括@ExceptionHandler注解桐腌。

當(dāng)一個(gè)未捕獲的Exception在DispatcherServlet處理請(qǐng)求的過(guò)程中發(fā)生時(shí)拄显,Spring會(huì)使用該接口的實(shí)現(xiàn)來(lái)處理Exception。該接口唯一的方法resolveException抽象了Exception轉(zhuǎn)換為ModelAndView的過(guò)程案站,方法簽名是這樣的:

ModelAndViewresolveException(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex)

Spring允許多個(gè)該接口的實(shí)現(xiàn)同時(shí)工作躬审,Spring會(huì)將已注冊(cè)的實(shí)現(xiàn)根據(jù)order排序后順序調(diào)用,直到某一個(gè)實(shí)現(xiàn)返回了非空結(jié)果蟆盐,這時(shí)Spring會(huì)終止調(diào)用鏈并返回ModelAndView承边。

缺省情況下,ExceptionHandlerExceptionResolver石挂、ResponseStatusExceptionResolver博助、DefaultHandlerExceptionResolver(排名根據(jù)優(yōu)先級(jí)從高到低)這三個(gè)實(shí)現(xiàn)會(huì)被注冊(cè)到Spring。

Spring內(nèi)置的HandlerExceptionResolver實(shí)現(xiàn)

Spring一共有以下4個(gè)典型的HandlerExceptionResolver實(shí)現(xiàn):

SimpleMappingExceptionResolver

該實(shí)現(xiàn)需要你配置一個(gè)Exception類名到視圖的映射清單痹愚,他會(huì)基于你的配置將Exception映射為視圖并返回browser富岳。

你的配置看起是這樣的,

java.sql.SqlException=sql_error_view

BizException=biz_error_view

除此之外拯腮,該實(shí)現(xiàn)還允許你定義視圖和response status的映射窖式、要排除的Exception、缺省異常視圖动壤、缺省response status等萝喘。(注意,這里的response status不用于HttpServletResponse.sendError琼懊,只用來(lái)HttpServletResponse.setStatus)

缺省情況下該實(shí)現(xiàn)并沒有注冊(cè)到Spring阁簸,你需要手動(dòng)將他注冊(cè)到Spring并進(jìn)行必要的配置才可使用。

ResponseStatusExceptionResolver

該實(shí)現(xiàn)并沒有明確指定返回什么視圖給browser哼丈,只是根據(jù)拋出的Exception類的@ResponseStatus注解启妹,調(diào)用HttpServletResponse.sendError方法通知servlet容器處理該response status。

你可以這樣聲明一個(gè)自定義Exception削祈,并在用戶無(wú)權(quán)限時(shí)在Controller中拋出:

@ResponseStatus(value=HttpStatus.FORBIDDEN)publicclassAuthzExceptionextendsRuntimeException{//...}

需要注意的是翅溺,這時(shí)如果你沒有配置servlet容器的error-page,servlet容器會(huì)返回缺省的異常頁(yè)面給browser髓抑。

這往往不是我們希望看到的咙崎,所以在使用@ResponseStatus注解時(shí),我們一般要配合error-page或反向代理使用吨拍,在下面會(huì)有相關(guān)的介紹褪猛。

DefaultHandlerExceptionResolver

這個(gè)類實(shí)現(xiàn)了Spring內(nèi)部的Exception如何映射到response status,并調(diào)用HttpServletResponse.sendError方法通知servlet容器處理羹饰。

如Spring會(huì)在請(qǐng)求的http method和@RequestMapping聲明的都不匹配時(shí)拋出org.springframework.web.HttpRequestMethodNotSupportedException伊滋,該實(shí)現(xiàn)收到后會(huì)將異常轉(zhuǎn)換為response status 405并調(diào)用HttpServletResponse.sendError。

有的同學(xué)就會(huì)有疑問(wèn)了队秩,這里Spring自己為什么不用@ResponseStatus注解笑旺?觀察代碼就會(huì)發(fā)現(xiàn),這里不僅僅是將Exception簡(jiǎn)單的映射到response status馍资,還會(huì)針對(duì)不同的Exception有不同的處理(response.setHeader)和選擇性的記錄日志筒主,這些是@ResponseStatus注解不能滿足的。

因?yàn)樵搶?shí)現(xiàn)和上面ResponseStatusExceptionResolver一樣只是調(diào)用HttpServletResponse.sendError方法通知servlet容器處理鸟蟹,所以你同樣需要考慮配合error-page或反向代理返回自定義異常視圖給browser乌妙。

亦或者你想改變sendError這一處理方式,比如直接返回自定義視圖給browser(其實(shí)完全可以在error-page中再統(tǒng)一處理建钥,除非你很在意這點(diǎn)性能的話)藤韵。這時(shí)你可以通過(guò)@ExceptionHandler注解(因?yàn)閮?yōu)先級(jí)的原因,@ExceptionHandler用于處理Spring內(nèi)部異常時(shí)優(yōu)先級(jí)高于該實(shí)現(xiàn))或繼承ResponseEntityExceptionHandler(也是基于@ExceptionHandler實(shí)現(xiàn))自己實(shí)現(xiàn)Spring內(nèi)部Exception的處理熊经。

ExceptionHandlerExceptionResolver

這個(gè)就是@ExceptionHandler注解的處理實(shí)現(xiàn)類泽艘,它是一個(gè)high-level的實(shí)現(xiàn),下面會(huì)專門說(shuō)奈搜。

自己實(shí)現(xiàn)HandlerExceptionResolver

當(dāng)然悉盆,如果上面的實(shí)現(xiàn)都滿足不了需求,你也可以自己實(shí)現(xiàn)HandlerExceptionResolver馋吗,并使用order控制他與其它實(shí)現(xiàn)的執(zhí)行優(yōu)先順序焕盟。

通過(guò)@ExceptionHandler注解處理異常

相對(duì)于HandlerExceptionResolver來(lái)言,這是一個(gè)high-level的處理方式宏粤。因?yàn)槟慊静辉傩枰虷ttpServletRequest脚翘、HttpServletResponse這種底層API打交道,而是像編寫Controller方法一樣使用Spring Controller的幾乎所有注解來(lái)處理并返回異常(比如@ResponseBody)绍哎。這就意味著来农,不管是根據(jù)http請(qǐng)求頭的accept返回不同的content type,還是讀寫request崇堰、session都將變的非常簡(jiǎn)單沃于。

需要注意的是涩咖,@ExceptionHandler方法的位置決定了他的作用范圍,如果寫在Controller中那么他的作用域就是當(dāng)前Controller繁莹,如果寫在ControllerAdvice中那么他的作用域就是ControllerAdvice的作用域(未特殊指定的ControllerAdvice就表示作用于全部Controller)檩互。

/**

* 處理RestController產(chǎn)生的異常,返回json咨演。

* @see ErrorController 處理非RestController產(chǎn)生的異常闸昨,返回html視圖。

*

* @author zaoheng.lb

*/@ControllerAdvice(annotations=RestController.class)publicclassRestErrorController{/**

? ? * 根據(jù)異常類型匹配處理spring mvc拋出的指定異常薄风。

? ? *

? ? * 處理下述情況:

? ? *? 1饵较、spring mvc內(nèi)部異常(如conversion-service、jsr-303 validator)

? ? *? 2遭赂、Controller中業(yè)務(wù)代碼的BusinessException異常循诉。

? ? *

? ? * @param ex

? ? * @return

? ? */@ExceptionHandler({TypeMismatchException.class,BindException.class,BusinessException.class})@ResponseBodypublicResponsehandleException(Exception ex){Response response=createResponse(ex);returnresponse;}}

Spring MVC之外的異常處理

上面說(shuō)的都是在Spring MVC之內(nèi)的異常處理,但是在DispatcherServlet之外也需要處理異常撇他,比如filter Exception和HttpServletResponse.sendError產(chǎn)生的異常response status打洼,這些如何處理呢?

servlet error-page

servlet規(guī)范中的error-page就是設(shè)計(jì)用來(lái)處理拋出到容器級(jí)別的Exception和異常response status的逆粹。他支持異常類型和異常response status到異常處理url的配置募疮,也支持缺省的異常處理url配置(用來(lái)兜底處理未配置的異常類型和異常response status)。

這是一個(gè)用web.xml來(lái)配置error-page的示例:

404/404java.sql.SqlException/sqlError/error

你可以編寫一個(gè)Controller響應(yīng)“/error”這個(gè)url來(lái)統(tǒng)一的處理Exception和異常response status僻弹,Exception對(duì)象等信息可以通過(guò)request attribute拿到(如有)阿浓。

Spring boot應(yīng)用

如果你的應(yīng)用是Spring boot應(yīng)用,那么恭喜你蹋绽,你不再需要自己配置error-page和實(shí)現(xiàn)異常處理芭毙,因?yàn)檫@些Spring都幫你實(shí)現(xiàn)好了(包括根據(jù)accept返回html或json)。你需要做的僅僅是在視圖文件夾(velocity的話就是spring.velocity.resource-loader-path這個(gè)配置)下新建一個(gè)error文件夾卸耘,再將編寫好的異常頁(yè)面根據(jù)response status命名后放到這里即可退敦。

例如你的視圖文件夾是templates的話,你的異常視圖文件結(jié)構(gòu)應(yīng)該是這樣的:

src/

+- main/

? ? +- java/

? ? |? +

? ? +- resources/

? ? ? ? +- templates/

? ? ? ? ? ? +- error/

? ? ? ? ? ? |? +- 404.vm

? ? ? ? ? ? |? +- 5xx.vm

? ? ? ? ? ? +-

當(dāng)然如果Spring boot的默認(rèn)實(shí)現(xiàn)不滿足你的需求(比如json屬性名稱不滿足)蚣抗,你可以繼承并修改他的行為侈百。詳見org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration。

反向代理

nginx等反向代理可以在頁(yè)面返回browser之前對(duì)頁(yè)面進(jìn)行修改翰铡,所以在nginx中配置error_page也可以達(dá)到將異常response status轉(zhuǎn)換為異常頁(yè)面返回browser的目的钝域。但在nginx中,你無(wú)法方便的獲取到Java Exception對(duì)象等信息锭魔。

error_page 404? ? ? ? /404.html;

error_page 502 503? ? /5xx.html;

總結(jié)

個(gè)人認(rèn)為例证,最佳實(shí)踐是多種方式配合使用,達(dá)到完善的異常處理效果迷捧。

方式處理Exception處理異常response status

HandlerExceptionResolver(包括@ExceptionHandler)支持不支持

servlet error-page支持支持

反向代理不支持支持

使用@ExceptionHandler注解處理Controller的Exception:在ExceptionHandler里我們一定可以拿到Exception對(duì)象织咧,所以你可以根據(jù)Exception對(duì)象返回異常視圖給browser胀葱。

使用servlet error-page兜底處理非Controller Exception和sendError產(chǎn)生的異常response status:此時(shí)不一定有Exception對(duì)象(如404),所以你可以根據(jù)response status返回異常視圖給browser笙蒙。

使用nginx配置一些特殊的異常response status:如502的異常頁(yè)面巡社,配置后可以防止servlet容器在重啟時(shí)用戶看到nginx的缺省異常頁(yè)面。

以上手趣,歡迎討論和指正。(* ̄︶ ̄)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肥荔,一起剝皮案震驚了整個(gè)濱河市绿渣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌燕耿,老刑警劉巖中符,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異誉帅,居然都是意外死亡淀散,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蚜锨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)档插,“玉大人,你說(shuō)我怎么就攤上這事亚再」牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵氛悬,是天一觀的道長(zhǎng)则剃。 經(jīng)常有香客問(wèn)我,道長(zhǎng)如捅,這世上最難降的妖魔是什么棍现? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮镜遣,結(jié)果婚禮上己肮,老公的妹妹穿的比我還像新娘。我一直安慰自己悲关,他們只是感情好朴肺,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坚洽,像睡著了一般戈稿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讶舰,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天鞍盗,我揣著相機(jī)與錄音需了,去河邊找鬼。 笑死般甲,一個(gè)胖子當(dāng)著我的面吹牛肋乍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼财松,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼媒区!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起觅闽,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涮俄,沒想到半個(gè)月后蛉拙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡彻亲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年孕锄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苞尝。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畸肆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宙址,到底是詐尸還是另有隱情恼除,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布曼氛,位于F島的核電站豁辉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏舀患。R本人自食惡果不足惜徽级,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望聊浅。 院中可真熱鬧餐抢,春花似錦、人聲如沸低匙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)顽冶。三九已至欺抗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間强重,已是汗流浹背绞呈。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工贸人, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人佃声。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓艺智,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親圾亏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子十拣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容