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/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
文中难免有不足, 欢迎指正
来源: https://www.cnblogs.com/Java-Starter/p/10356055.html