使用Spring MVC統(tǒng)一異常處理實戰(zhàn)

(轉載) 原文連接

1 描述

在J2EE項目的開發(fā)中,不管是對底層的數據庫操作過程梅惯,還是業(yè)務層的處理過程,還是控制層的處理過程,都不可避免會遇到各種可預知的窃蹋、不可預知的異常需要處理者铜。每個過程都單獨處理異常,系統(tǒng)的代碼耦合度高域仇,工作量大且不好統(tǒng)一刑然,維護的工作量也很大。
那么暇务,能不能將所有類型的異常處理從各處理過程解耦出來泼掠,這樣既保證了相關處理過程的功能較單一,也實現了異常信息的統(tǒng)一處理和維護垦细?答案是肯定的择镇。下面將介紹使用Spring MVC統(tǒng)一處理異常的解決和實現過程。

2 分析

Spring MVC處理異常有3種方式:

  1. 使用Spring MVC提供的簡單異常處理器SimpleMappingExceptionResolver括改;
  2. 實現Spring的異常處理接口HandlerExceptionResolver 自定義自己的異常處理器腻豌;
  3. 使用@ExceptionHandler注解實現異常處理;

3 實戰(zhàn)

3.1 引言

為了驗證Spring MVC的3種異常處理方式的實際效果嘱能,我們需要開發(fā)一個測試項目饲梭,從Dao層、Service層焰檩、Controller層分別拋出不同的異常憔涉,然后分別集成3種方式進行異常處理,從而比較3種方式的優(yōu)缺點析苫。

3.2 實戰(zhàn)項目

3.2.1 項目結構
image.png
image.png
3.2.2 異常類定義
/**  
 * 系統(tǒng)業(yè)務異常  
 */    
public class BusinessException extends RuntimeException {    
    
    /** serialVersionUID */    
    private static final long serialVersionUID = 2332608236621015980L;    
    
    private String code;    
    
    public BusinessException() {    
        super();    
    }    
    
    public BusinessException(String message) {    
        super(message);    
    }    
    
    public BusinessException(String code, String message) {    
        super(message);    
        this.code = code;    
    }    
    
    public BusinessException(Throwable cause) {    
        super(cause);    
    }    
    
    public BusinessException(String message, Throwable cause) {    
        super(message, cause);    
    }    
    
    public BusinessException(String code, String message, Throwable cause) {    
        super(message, cause);    
        this.code = code;    
    }    
    
    public String getCode() {    
        return code;    
    }    
    
    public void setCode(String code) {    
        this.code = code;    
    }    
    
}    
    
    
public class ParameterException extends RuntimeException {    
    
    /** serialVersionUID */    
    private static final long serialVersionUID = 6417641452178955756L;    
    
    public ParameterException() {    
        super();    
    }    
    
    public ParameterException(String message) {    
        super(message);    
    }    
    
    public ParameterException(Throwable cause) {    
        super(cause);    
    }    
    
    public ParameterException(String message, Throwable cause) {    
        super(message, cause);    
    }    
}  
3.2.3 Dao層代碼
@Repository("testDao")  
public class TestDao {  
    public void exception(Integer id) throws Exception {  
        switch(id) {  
        case 1:  
            throw new BusinessException("12", "dao12");  
        case 2:  
            throw new BusinessException("22", "dao22");  
        case 3:  
            throw new BusinessException("32", "dao32");  
        case 4:  
            throw new BusinessException("42", "dao42");  
        case 5:  
            throw new BusinessException("52", "dao52");  
        default:  
            throw new ParameterException("Dao Parameter Error");  
        }  
    }  
}  
3.2.4 Service層代碼
public interface TestService {  
    public void exception(Integer id) throws Exception;  
      
    public void dao(Integer id) throws Exception;  
}  
  
@Service("testService")  
public class TestServiceImpl implements TestService {  
    @Resource  
    private TestDao testDao;  
      
    public void exception(Integer id) throws Exception {  
        switch(id) {  
        case 1:  
            throw new BusinessException("11", "service11");  
        case 2:  
            throw new BusinessException("21", "service21");  
        case 3:  
            throw new BusinessException("31", "service31");  
        case 4:  
            throw new BusinessException("41", "service41");  
        case 5:  
            throw new BusinessException("51", "service51");  
        default:  
            throw new ParameterException("Service Parameter Error");  
        }  
    }  
  
    @Override  
    public void dao(Integer id) throws Exception {  
        testDao.exception(id);  
    }  
}  
3.2.5 Controller層代碼
@Controller  
public class TestController {  
    @Resource  
    private TestService testService;  
      
    @RequestMapping(value = "/controller.do", method = RequestMethod.GET)  
    public void controller(HttpServletResponse response, Integer id) throws Exception {  
        switch(id) {  
        case 1:  
            throw new BusinessException("10", "controller10");  
        case 2:  
            throw new BusinessException("20", "controller20");  
        case 3:  
            throw new BusinessException("30", "controller30");  
        case 4:  
            throw new BusinessException("40", "controller40");  
        case 5:  
            throw new BusinessException("50", "controller50");  
        default:  
            throw new ParameterException("Controller Parameter Error");  
        }  
    }  
      
    @RequestMapping(value = "/service.do", method = RequestMethod.GET)  
    public void service(HttpServletResponse response, Integer id) throws Exception {  
        testService.exception(id);  
    }  
      
    @RequestMapping(value = "/dao.do", method = RequestMethod.GET)  
    public void dao(HttpServletResponse response, Integer id) throws Exception {  
        testService.dao(id);  
    }  
}  
3.2.6 JSP頁面代碼
<%@ page contentType="text/html; charset=UTF-8"%>  
<html>  
<head>  
<title>Maven Demo</title>  
</head>  
<body>  
<h1>所有的演示例子</h1>  
<h3>[url=./dao.do?id=1]Dao正常錯誤[/url]</h3>  
<h3>[url=./dao.do?id=10]Dao參數錯誤[/url]</h3>  
<h3>[url=./dao.do?id=]Dao未知錯誤[/url]</h3>  
  
  
<h3>[url=./service.do?id=1]Service正常錯誤[/url]</h3>  
<h3>[url=./service.do?id=10]Service參數錯誤[/url]</h3>  
<h3>[url=./service.do?id=]Service未知錯誤[/url]</h3>  
  
  
<h3>[url=./controller.do?id=1]Controller正常錯誤[/url]</h3>  
<h3>[url=./controller.do?id=10]Controller參數錯誤[/url]</h3>  
<h3>[url=./controller.do?id=]Controller未知錯誤[/url]</h3>  
  
  
<h3>[url=./404.do?id=1]404錯誤[/url]</h3>  
</body>  
</html>  

3.3 集成異常處理

3.3.1 使用SimpleMappingExceptionResolver實現異常處理
  1. 在Spring的配置文件applicationContext.xml中增加以下內容:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">  
    <!-- 定義默認的異常處理頁面兜叨,當該異常類型的注冊時使用 -->  
    <property name="defaultErrorView" value="error"></property>  
    <!-- 定義異常處理頁面用來獲取異常信息的變量名,默認名為exception -->  
    <property name="exceptionAttribute" value="ex"></property>  
    <!-- 定義需要特殊處理的異常衩侥,用類名或完全路徑名作為key国旷,異常也頁名作為值 -->  
    <property name="exceptionMappings">  
        <props>  
            <prop key="cn.basttg.core.exception.BusinessException">error-business</prop>  
            <prop key="cn.basttg.core.exception.ParameterException">error-parameter</prop>  
  
            <!-- 這里還可以繼續(xù)擴展對不同異常類型的處理 -->  
        </props>  
    </property>  
</bean>  
  1. 啟動測試項目,經驗證茫死,Dao層跪但、Service層、Controller層拋出的異常(業(yè)務異常BusinessException峦萎、參數異常ParameterException和其它的異常Exception)都能準確顯示定義的異常處理頁面屡久,達到了統(tǒng)一異常處理的目標忆首。

  2. 從上面的集成過程可知,使用SimpleMappingExceptionResolver進行異常處理被环,具有集成簡單糙及、有良好的擴展性、對已有代碼沒有入侵性等優(yōu)點筛欢,但該方法僅能獲取到異常信息浸锨,若在出現異常時,對需要獲取除異常以外的數據的情況不適用版姑。

3.3.2 實現HandlerExceptionResolver 接口自定義異常處理器
  1. 增加HandlerExceptionResolver 接口的實現類MyExceptionHandler柱搜,代碼如下:
public class MyExceptionHandler implements HandlerExceptionResolver {  
  
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,  
            Exception ex) {  
        Map<String, Object> model = new HashMap<String, Object>();  
        model.put("ex", ex);  
          
        // 根據不同錯誤轉向不同頁面  
        if(ex instanceof BusinessException) {  
            return new ModelAndView("error-business", model);  
        }else if(ex instanceof ParameterException) {  
            return new ModelAndView("error-parameter", model);  
        } else {  
            return new ModelAndView("error", model);  
        }  
    }  
}  
  1. 在Spring的配置文件applicationContext.xml中增加以下內容:
<bean id="exceptionHandler" class="cn.basttg.core.exception.MyExceptionHandler"/>  
  1. 啟動測試項目,經驗證剥险,Dao層聪蘸、Service層、Controller層拋出的異常(業(yè)務異常BusinessException炒嘲、參數異常ParameterException和其它的異常Exception)都能準確顯示定義的異常處理頁面宇姚,達到了統(tǒng)一異常處理的目標匈庭。
  2. 從上面的集成過程可知夫凸,使用實現HandlerExceptionResolver接口的異常處理器進行異常處理,具有集成簡單阱持、有良好的擴展性夭拌、對已有代碼沒有入侵性等優(yōu)點,同時衷咽,在異常處理時能獲取導致出現異常的對象鸽扁,有利于提供更詳細的異常處理信息。
3.3.3 使用@ExceptionHandler注解實現異常處理
  1. 增加BaseController類镶骗,并在類中使用@ExceptionHandler注解聲明異常處理桶现,代碼如下:
public class BaseController {  
    /** 基于@ExceptionHandler異常處理 */  
    @ExceptionHandler  
    public String exp(HttpServletRequest request, Exception ex) {  
          
        request.setAttribute("ex", ex);  
          
        // 根據不同錯誤轉向不同頁面  
        if(ex instanceof BusinessException) {  
            return "error-business";  
        }else if(ex instanceof ParameterException) {  
            return "error-parameter";  
        } else {  
            return "error";  
        }  
    }  
}  
  1. 修改代碼,使所有需要異常處理的Controller都繼承該類鼎姊,如下所示骡和,修改后的TestController類繼承于BaseController:
public class TestController extends BaseController  
  1. 啟動測試項目,經驗證相寇,Dao層慰于、Service層、Controller層拋出的異常(業(yè)務異常BusinessException唤衫、參數異常ParameterException和其它的異常Exception)都能準確顯示定義的異常處理頁面婆赠,達到了統(tǒng)一異常處理的目標。

  2. 從上面的集成過程可知佳励,使用@ExceptionHandler注解實現異常處理休里,具有集成簡單蛆挫、有擴展性好(只需要將要異常處理的Controller類繼承于BaseController即可)、不需要附加Spring配置等優(yōu)點份帐,但該方法對已有代碼存在入侵性(需要修改已有代碼璃吧,使相關類繼承于BaseController),在異常處理時不能獲取除異常以外的數據废境。

3.4 未捕獲異常的處理

對于Unchecked Exception而言畜挨,由于代碼不強制捕獲,往往被忽略噩凹,如果運行期產生了Unchecked Exception巴元,而代碼中又沒有進行相應的捕獲和處理,則我們可能不得不面對尷尬的404驮宴、500……等服務器內部錯誤提示頁面逮刨。
我們需要一個全面而有效的異常處理機制。目前大多數服務器也都支持在Web.xml中通過<error-page>(Websphere/Weblogic)或者<error-code>(Tomcat)節(jié)點配置特定異常情況的顯示頁面堵泽。修改web.xml文件修己,增加以下內容:

<!-- 出錯頁面定義 -->  
<error-page>  
    <exception-type>java.lang.Throwable</exception-type>  
    <location>/500.jsp</location>  
</error-page>  
<error-page>  
    <error-code>500</error-code>  
    <location>/500.jsp</location>  
</error-page>  
<error-page>  
    <error-code>404</error-code>  
    <location>/404.jsp</location>  
</error-page>  
  
<!-- 這里可繼續(xù)增加服務器錯誤號的處理及對應顯示的頁面 -->  

4 解決結果

  1. 運行測試項目顯示的首頁,如下圖所示:
image.png
  1. 業(yè)務錯誤顯示的頁面迎罗,如下圖所示:
image.png
  1. 參數錯誤顯示的頁面睬愤,如下圖所示:
image.png
  1. 未知錯誤顯示的頁面,如下圖所示:
image.png
  1. 服務器內部錯誤頁面纹安,如下圖所示:
image.png

5 總結

綜合上述可知尤辱,Spring MVC集成異常處理3種方式都可以達到統(tǒng)一異常處理的目標。從3種方式的優(yōu)缺點比較厢岂,若只需要簡單的集成異常處理光督,推薦使用SimpleMappingExceptionResolver即可;若需要集成的異常處理能夠更具個性化塔粒,提供給用戶更詳細的異常信息结借,推薦自定義實現HandlerExceptionResolver接口的方式;若不喜歡Spring配置文件或要實現“零配置”卒茬,且能接受對原有代碼的適當入侵船老,則建議使用@ExceptionHandler注解方式。

6 源代碼

源代碼項目如下所示扬虚,為Maven項目努隙,若需運行,請自行獲取相關的依賴包辜昵。
點擊這里獲取源代碼

7 參考資料

[1] Spring MVC統(tǒng)一處理異常的方法
http://hi.baidu.com/99999999hao/blog/item/25da70174bfbf642f919b8c3.html
[2] SpringMVC 異常處理初探
http://exceptioneye.iteye.com/blog/1306150
[3] Spring3 MVC 深入研究
http://elf8848.iteye.com/blog/875830
[4] Spring MVC異常處理
http://blog.csdn.net/rj042/article/details/7380442

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末荸镊,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌躬存,老刑警劉巖张惹,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異岭洲,居然都是意外死亡宛逗,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門盾剩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雷激,“玉大人,你說我怎么就攤上這事告私∈合荆” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵驻粟,是天一觀的道長根悼。 經常有香客問我,道長蜀撑,這世上最難降的妖魔是什么挤巡? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮酷麦,結果婚禮上矿卑,老公的妹妹穿的比我還像新娘。我一直安慰自己贴铜,他們只是感情好粪摘,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布瀑晒。 她就那樣靜靜地躺著绍坝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苔悦。 梳的紋絲不亂的頭發(fā)上轩褐,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音玖详,去河邊找鬼把介。 笑死,一個胖子當著我的面吹牛蟋座,可吹牛的內容都是我干的拗踢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼向臀,長吁一口氣:“原來是場噩夢啊……” “哼巢墅!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤君纫,失蹤者是張志新(化名)和其女友劉穎驯遇,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體蓄髓,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡叉庐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了会喝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陡叠。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肢执,靈堂內的尸體忽然破棺而出匾竿,到底是詐尸還是另有隱情,我是刑警寧澤蔚万,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布岭妖,位于F島的核電站,受9級特大地震影響反璃,放射性物質發(fā)生泄漏昵慌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一淮蜈、第九天 我趴在偏房一處隱蔽的房頂上張望斋攀。 院中可真熱鬧,春花似錦梧田、人聲如沸淳蔼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹉梨。三九已至,卻和暖如春穿稳,著一層夾襖步出監(jiān)牢的瞬間存皂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工逢艘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旦袋,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓它改,卻偏偏與公主長得像疤孕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子央拖,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理祭阀,服務發(fā)現截亦,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,809評論 6 342
  • 本章主要內容: 備用的Spring MVC配置項 處理文件上傳 控制器中的異常處理 使用flash屬性 “等等柬讨,客...
    hoxis閱讀 3,521評論 1 26
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風情閱讀 1,672評論 0 3
  • 一崩瓤、春 かわいらしい郊外電車の沿線には 楽しげに白い家々があつた 散歩を誘う小徑があつた 降りもしない 乗りもしな...
    Hippocrene閱讀 585評論 0 0