SpringMVC源碼閱讀:異常解析器


1.前言

SpringMVC是目前J2EE平台的主流Web框架,不熟悉的園友可以看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧

本文將通過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC如何完成異常解析、捕捉異常,並自定義異常和異常解析器

2.源碼分析

進入DispatcherServlet的processDispatchResult方法

1024行判斷異常是否是ModelAndViewDefiningException類型,如果是,直接返回ModelAndView

不是ModelAndViewDefiningException類型,則獲取HandlerMethod,調用processHandlerExeception方法

點進去1030行的processHandlerException方法,該方法根據HandlerExecutionResolvers來解析異常並選擇ModelAndView

1217行遍歷HandlerExceptionResolvers,我們講過,在<mvc:annotation-driven/>幫我們注冊了默認的異常解析器

請看AnnotationDrivenBeanDefinitionParser(解析annotation-driven的類)

1218行調用HandlerExceptionResolver的resolveException方法,該方法被子類AbstractHandlerExceptionResolver實現

1225行給request設置異常信息

現在進入HandlerExceptionResolver接口resolveException方法的實現處——AbstractHandlerExceptionResolver的resolveException方法

131行判斷該異常解析器是否可以被應用到Handler

135行為異常情況准備response,即給response添加頭部

136行調用抽象方法doResolveException,由子類實現

進入AbstractHandlerMethodExceptionResolver的doResolveException方法

59行調用抽象方法,被子類ExceptionHandlerExceptionResolver實現

打開ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法

362行獲取有異常的Controller方法

367~368行為ServletInvocableHandlerMethod設置HandlerMethodArgumentResolverComposite和HandlerMethodReturnValueComposite,用來解析參數和處理返回值

380行調用invokeAndHandle方法處理返回值,暴露cause

384行無cause

3.實例

3.1 使用@ResponseStatus自定義異常UnauthorizedException

@ResponseStatus會被ResponseStatusExceptionResolver解析

@ResponseStatus(code=HttpStatus.UNAUTHORIZED,reason="用戶未授權")
public class UnauthorizedException extends RuntimeException {

}

測試方法

    @RequestMapping("/unauth")
    public Map unauth() {
        throw new UnauthorizedException();
    }

瀏覽器輸入http://localhost:8080/springmvcdemo/error/unauth

3.2 無注解情況

測試方法

    @RequestMapping("/noSuchMethod")
    public Map noHandleMethod() throws NoSuchMethodException {
        throw new NoSuchMethodException();
    }

沒有@ExceptionHandler和@ResponseStatus注解則會被DefaultHandlerExceptionResolver解析

瀏覽器輸入http://localhost:8080/springmvcdemo/error/noSuchMethod

3.3 @ExceptionHandler處理異常

測試方法

@ExceptionHandler會被ExceptionHandlerExceptionResolver解析

    @RequestMapping("/exception")
    @ResponseBody
    public Map exception() throws ClassNotFoundException {
        throw new ClassNotFoundException("class not found");
    }

    @RequestMapping("/nullpointer")
    @ResponseBody
    public Map nullpointer() {
        Map resultMap = new HashMap();
        String str = null;
        str.length();
        resultMap.put("strNullError",str);
        return resultMap;
    }

    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public Map error(RuntimeException error, HttpServletRequest request) {
        Map resultMap = new HashMap();
        resultMap.put("param", "Runtime error");
        return resultMap;
    }

    @ExceptionHandler()
    @ResponseBody
    public Map error(Exception error, HttpServletRequest request, HttpServletResponse response) {
        Map resultMap = new HashMap();
        resultMap.put("param", "Exception error");
        return resultMap;
    }

瀏覽器輸入http://localhost:8080/springmvcdemo/error/classNotFound

瀏覽器輸入http://localhost:8080/springmvcdemo/error/nullpointer

根據異常類繼承關系,ClassNotFoundException離Exception更近,所以被@ExceptionHandler()的error方法解析,注解無參相當於Exception.class。

同理,NullPointerException方法離NullPointerException“最近”,把@ExceptionHandler(NullPointerException.class)的error方法注釋掉,瀏覽器輸入http://localhost:8080/springmvcdemo/error/nullpointer,會發現

瀏覽器返回RuntimeException,印證了我們的說法

3.4 定義全局異常處理

/**
 * @Author: 谷天樂
 * @Date: 2019/1/21 10:48
 * @Description: ExceptionHandlerMethodResolver內部找不到Controller的@ExceptionHandler注解的話,
 * 會找@ControllerAdvice中的@ExceptionHandler注解方法
 */
@ControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler(Throwable.class)
    @ResponseBody
    public Map<String, Object> ajaxError(Throwable error, HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("error", error.getMessage());
        map.put("result", "error");
        return map;
    }

}

瀏覽器輸入http://localhost:8080/springmvcdemo/error/unauth

 

優先級關系:@ExceptionHandler>@ControllerAdvice中的@ExceptionHandler>@ResponseStatus

要把TestErrorController中@ExceptionHandler的方法注釋掉才會有效果

4.總結

HandlerExceptionResolver作為異常解析器的接口,核心方法是resolveException

AbstractHandlerExceptionResolver實現HandlerException,resolveException方法內部調用抽象方法doResolveException,該方法被子類實現;shouldApplyTo方法檢查該異常解析器是否可以被應用到Handler

AbstractHandlerMethodExceptionResolver的doResolveException內部調用抽象方法doResolveHandlerMethodException,由子類實現,返回ModelAndView,可以在視圖模型里自定義錯誤頁面;shouldApplyTo調用父類方法

ExceptionHandlerExceptionResovler的doResolveHandlerMethodException處理異常,返回ModelAndView

DefaultHandlerExceptionResolver的doResolveException處理默認異常

ResponseStatusExceptionResolver的doResolveException方法處理@ResponseStatus修飾的異常

DispatcherServlet的processHandlerException方法根據注冊的HandlerExceptionResolvers選擇一個ModelAndView

DispatcherServlet的doDispatch方法調用processDispatchResult,該方法處理Handler的選擇和調用的結果,processDispatchResult方法調用processHandlerException

5.參考

https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#beans-beans-conversion

https://docs.spring.io/spring/docs/current/javadoc-api/

https://github.com/spring-projects/spring-framework

文中難免有不足,歡迎指正

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM