SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html
本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC如何完成異常解析、捕捉異常,並自定義異常和異常解析器java
進入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
@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
測試方法
@RequestMapping("/noSuchMethod") public Map noHandleMethod() throws NoSuchMethodException { throw new NoSuchMethodException(); }
沒有@ExceptionHandler和@ResponseStatus註解則會被DefaultHandlerExceptionResolver解析
瀏覽器輸入http://localhost:8080/springmvcdemo/error/noSuchMethod
測試方法
@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,印證了咱們的說法
/** * @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的方法註釋掉纔會有效果
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
https://docs.spring.io/spring/docs/current/javadoc-api/
https://github.com/spring-projects/spring-framework
文中不免有不足,歡迎指正