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行遍历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
文中难免有不足,欢迎指正
作者:Rest探路者
出处:http://www.cnblogs.com/Java-Starter/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意请保留此段声明,请在文章页面明显位置给出原文连接
Github:https://github.com/cjy513203427