1. 程式人生 > >使用Spring MVC統一異常處理實戰

使用Spring MVC統一異常處理實戰

tro 處理機制 tor attr 運行 target icon message 404錯誤

原文地址:http://cgs1999.iteye.com/blog/1547197

1 描述
在J2EE項目的開發中,不管是對底層的數據庫操作過程,還是業務層的處理過程,還是控制層的處理過程,都不可避免會遇到各種可預知的、不可預知的異常需要處理。每個過程都單獨處理異常,系統的代碼耦合度高,工作量大且不好統一,維護的工作量也很大。
那麽,能不能將所有類型的異常處理從各處理過程解耦出來,這樣既保證了相關處理過程的功能較單一,也實現了異常信息的統一處理和維護?答案是肯定的。下面將介紹使用Spring MVC統一處理異常的解決和實現過程。

2 分析
Spring MVC處理異常有3種方式:
(1)使用Spring MVC提供的簡單異常處理器SimpleMappingExceptionResolver;

(2)實現Spring的異常處理接口HandlerExceptionResolver 自定義自己的異常處理器;
(3)[email protected]

3 實戰
3.1 引言
為了驗證Spring MVC的3種異常處理方式的實際效果,我們需要開發一個測試項目,從Dao層、Service層、Controller層分別拋出不同的異常,然後分別集成3種方式進行異常處理,從而比較3種方式的優缺點。

3.2 實戰項目
3.2.1 項目結構
技術分享技術分享

3.2.2 異常類定義

Java代碼 技術分享
  1. /**
  2. * 系統業務異常
  3. */
  4. public class BusinessException extends RuntimeException {
  5. /** serialVersionUID */
  6. private static final long serialVersionUID = 2332608236621015980L;
  7. private String code;
  8. public BusinessException() {
  9. super();
  10. }
  11. public BusinessException(String message) {
  12. super(message);
  13. }
  14. public BusinessException(String code, String message) {
  15. super(message);
  16. this.code = code;
  17. }
  18. public BusinessException(Throwable cause) {
  19. super(cause);
  20. }
  21. public BusinessException(String message, Throwable cause) {
  22. super(message, cause);
  23. }
  24. public BusinessException(String code, String message, Throwable cause) {
  25. super(message, cause);
  26. this.code = code;
  27. }
  28. public String getCode() {
  29. return code;
  30. }
  31. public void setCode(String code) {
  32. this.code = code;
  33. }
  34. }
  35. public class ParameterException extends RuntimeException {
  36. /** serialVersionUID */
  37. private static final long serialVersionUID = 6417641452178955756L;
  38. public ParameterException() {
  39. super();
  40. }
  41. public ParameterException(String message) {
  42. super(message);
  43. }
  44. public ParameterException(Throwable cause) {
  45. super(cause);
  46. }
  47. public ParameterException(String message, Throwable cause) {
  48. super(message, cause);
  49. }
  50. }


3.2.3 Dao層代碼

Java代碼 技術分享
  1. @Repository("testDao")
  2. public class TestDao {
  3. public void exception(Integer id) throws Exception {
  4. switch(id) {
  5. case 1:
  6. throw new BusinessException("12", "dao12");
  7. case 2:
  8. throw new BusinessException("22", "dao22");
  9. case 3:
  10. throw new BusinessException("32", "dao32");
  11. case 4:
  12. throw new BusinessException("42", "dao42");
  13. case 5:
  14. throw new BusinessException("52", "dao52");
  15. default:
  16. throw new ParameterException("Dao Parameter Error");
  17. }
  18. }
  19. }


3.2.4 Service層代碼

Java代碼 技術分享
  1. public interface TestService {
  2. public void exception(Integer id) throws Exception;
  3. public void dao(Integer id) throws Exception;
  4. }
  5. @Service("testService")
  6. public class TestServiceImpl implements TestService {
  7. @Resource
  8. private TestDao testDao;
  9. public void exception(Integer id) throws Exception {
  10. switch(id) {
  11. case 1:
  12. throw new BusinessException("11", "service11");
  13. case 2:
  14. throw new BusinessException("21", "service21");
  15. case 3:
  16. throw new BusinessException("31", "service31");
  17. case 4:
  18. throw new BusinessException("41", "service41");
  19. case 5:
  20. throw new BusinessException("51", "service51");
  21. default:
  22. throw new ParameterException("Service Parameter Error");
  23. }
  24. }
  25. @Override
  26. public void dao(Integer id) throws Exception {
  27. testDao.exception(id);
  28. }
  29. }


3.2.5 Controller層代碼

Java代碼 技術分享
  1. @Controller
  2. public class TestController {
  3. @Resource
  4. private TestService testService;
  5. @RequestMapping(value = "/controller.do", method = RequestMethod.GET)
  6. public void controller(HttpServletResponse response, Integer id) throws Exception {
  7. switch(id) {
  8. case 1:
  9. throw new BusinessException("10", "controller10");
  10. case 2:
  11. throw new BusinessException("20", "controller20");
  12. case 3:
  13. throw new BusinessException("30", "controller30");
  14. case 4:
  15. throw new BusinessException("40", "controller40");
  16. case 5:
  17. throw new BusinessException("50", "controller50");
  18. default:
  19. throw new ParameterException("Controller Parameter Error");
  20. }
  21. }
  22. @RequestMapping(value = "/service.do", method = RequestMethod.GET)
  23. public void service(HttpServletResponse response, Integer id) throws Exception {
  24. testService.exception(id);
  25. }
  26. @RequestMapping(value = "/dao.do", method = RequestMethod.GET)
  27. public void dao(HttpServletResponse response, Integer id) throws Exception {
  28. testService.dao(id);
  29. }
  30. }


3.2.6 JSP頁面代碼

Java代碼 技術分享
  1. <%@ page contentType="text/html; charset=UTF-8"%>
  2. <html>
  3. <head>
  4. <title>Maven Demo</title>
  5. </head>
  6. <body>
  7. <h1>所有的演示例子</h1>
  8. <h3>[url=./dao.do?id=1]Dao正常錯誤[/url]</h3>
  9. <h3>[url=./dao.do?id=10]Dao參數錯誤[/url]</h3>
  10. <h3>[url=./dao.do?id=]Dao未知錯誤[/url]</h3>
  11. <h3>[url=./service.do?id=1]Service正常錯誤[/url]</h3>
  12. <h3>[url=./service.do?id=10]Service參數錯誤[/url]</h3>
  13. <h3>[url=./service.do?id=]Service未知錯誤[/url]</h3>
  14. <h3>[url=./controller.do?id=1]Controller正常錯誤[/url]</h3>
  15. <h3>[url=./controller.do?id=10]Controller參數錯誤[/url]</h3>
  16. <h3>[url=./controller.do?id=]Controller未知錯誤[/url]</h3>
  17. <h3>[url=./404.do?id=1]404錯誤[/url]</h3>
  18. </body>
  19. </html>


3.3 集成異常處理
3.3.1 使用SimpleMappingExceptionResolver實現異常處理
1、在Spring的配置文件applicationContext.xml中增加以下內容:

Xml代碼 技術分享
  1. <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
  2. <!-- 定義默認的異常處理頁面,當該異常類型的註冊時使用 -->
  3. <property name="defaultErrorView" value="error"></property>
  4. <!-- 定義異常處理頁面用來獲取異常信息的變量名,默認名為exception -->
  5. <property name="exceptionAttribute" value="ex"></property>
  6. <!-- 定義需要特殊處理的異常,用類名或完全路徑名作為key,異常也頁名作為值 -->
  7. <property name="exceptionMappings">
  8. <props>
  9. <prop key="cn.basttg.core.exception.BusinessException">error-business</prop>
  10. <prop key="cn.basttg.core.exception.ParameterException">error-parameter</prop>
  11. <!-- 這裏還可以繼續擴展對不同異常類型的處理 -->
  12. </props>
  13. </property>
  14. </bean>


2、啟動測試項目,經驗證,Dao層、Service層、Controller層拋出的異常(業務異常BusinessException、參數異常ParameterException和其它的異常Exception)都能準確顯示定義的異常處理頁面,達到了統一異常處理的目標。

3、從上面的集成過程可知,使用SimpleMappingExceptionResolver進行異常處理,具有集成簡單、有良好的擴展性、對已有代碼沒有入侵性等優點,但該方法僅能獲取到異常信息,若在出現異常時,對需要獲取除異常以外的數據的情況不適用。

3.3.2 實現HandlerExceptionResolver 接口自定義異常處理器
1、增加HandlerExceptionResolver 接口的實現類MyExceptionHandler,代碼如下:

Java代碼 技術分享
  1. public class MyExceptionHandler implements HandlerExceptionResolver {
  2. public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
  3. Exception ex) {
  4. Map<String, Object> model = new HashMap<String, Object>();
  5. model.put("ex", ex);
  6. // 根據不同錯誤轉向不同頁面
  7. if(ex instanceof BusinessException) {
  8. return new ModelAndView("error-business", model);
  9. }else if(ex instanceof ParameterException) {
  10. return new ModelAndView("error-parameter", model);
  11. } else {
  12. return new ModelAndView("error", model);
  13. }
  14. }
  15. }


2、在Spring的配置文件applicationContext.xml中增加以下內容:

Xml代碼 技術分享
  1. <bean id="exceptionHandler" class="cn.basttg.core.exception.MyExceptionHandler"/>


3、啟動測試項目,經驗證,Dao層、Service層、Controller層拋出的異常(業務異常BusinessException、參數異常ParameterException和其它的異常Exception)都能準確顯示定義的異常處理頁面,達到了統一異常處理的目標。

4、從上面的集成過程可知,使用實現HandlerExceptionResolver接口的異常處理器進行異常處理,具有集成簡單、有良好的擴展性、對已有代碼沒有入侵性等優點,同時,在異常處理時能獲取導致出現異常的對象,有利於提供更詳細的異常處理信息。

3.3.3 [email protected]
1、增加BaseController類,[email protected],代碼如下:

Java代碼 技術分享
  1. public class BaseController {
  2. /** [email protected] */
  3. @ExceptionHandler
  4. public String exp(HttpServletRequest request, Exception ex) {
  5. request.setAttribute("ex", ex);
  6. // 根據不同錯誤轉向不同頁面
  7. if(ex instanceof BusinessException) {
  8. return "error-business";
  9. }else if(ex instanceof ParameterException) {
  10. return "error-parameter";
  11. } else {
  12. return "error";
  13. }
  14. }
  15. }


2、修改代碼,使所有需要異常處理的Controller都繼承該類,如下所示,修改後的TestController類繼承於BaseController:

Java代碼 技術分享
  1. public class TestController extends BaseController


3、啟動測試項目,經驗證,Dao層、Service層、Controller層拋出的異常(業務異常BusinessException、參數異常ParameterException和其它的異常Exception)都能準確顯示定義的異常處理頁面,達到了統一異常處理的目標。

4、從上面的集成過程可知,[email protected],具有集成簡單、有擴展性好(只需要將要異常處理的Controller類繼承於BaseController即可)、不需要附加Spring配置等優點,但該方法對已有代碼存在入侵性(需要修改已有代碼,使相關類繼承於BaseController),在異常處理時不能獲取除異常以外的數據。

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

Xml代碼 技術分享
  1. <!-- 出錯頁面定義 -->
  2. <error-page>
  3. <exception-type>java.lang.Throwable</exception-type>
  4. <location>/500.jsp</location>
  5. </error-page>
  6. <error-page>
  7. <error-code>500</error-code>
  8. <location>/500.jsp</location>
  9. </error-page>
  10. <error-page>
  11. <error-code>404</error-code>
  12. <location>/404.jsp</location>
  13. </error-page>
  14. <!-- 這裏可繼續增加服務器錯誤號的處理及對應顯示的頁面 -->


4 解決結果
1、運行測試項目顯示的首頁,如下圖所示:
技術分享

2、業務錯誤顯示的頁面,如下圖所示:
技術分享

3、參數錯誤顯示的頁面,如下圖所示:
技術分享

4、未知錯誤顯示的頁面,如下圖所示:
技術分享

5、服務器內部錯誤頁面,如下圖所示:
技術分享

5 總結
綜合上述可知,Spring MVC集成異常處理3種方式都可以達到統一異常處理的目標。從3種方式的優缺點比較,若只需要簡單的集成異常處理,推薦使用SimpleMappingExceptionResolver即可;若需要集成的異常處理能夠更具個性化,提供給用戶更詳細的異常信息,推薦自定義實現HandlerExceptionResolver接口的方式;若不喜歡Spring配置文件或要實現“零配置”,且能接受對原有代碼的適當入侵,[email protected]

6 源代碼
源代碼項目如下所示,為Maven項目,若需運行,請自行獲取相關的依賴包。
點擊這裏獲取源代碼

7 參考資料
[1] Spring MVC統一處理異常的方法
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

使用Spring MVC統一異常處理實戰