SpringMVC源碼閱讀:異常解析器

1.前言

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

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

2.源碼分析

進入DispatcherServlet的processDispatchResult方法git

1024行判斷異常是不是ModelAndViewDefiningException類型,若是是,直接返回ModelAndViewgithub

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

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

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

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

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

1225行給request設置異常信息app

如今進入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

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

相關文章
相關標籤/搜索