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/current/javadoc-api/
https://github.com/spring-projects/spring-framework
文中難免有不足,歡迎指正