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行遍歷HandlerExecutionResolvers,我們講過,在<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修飾的異常
DispatcherServler的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
文中難免有不足,歡迎指正