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/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

时间: 2024-08-03 14:47:58

SpringMVC源码阅读:异常解析器的相关文章

SpringMVC源码阅读:拦截器

1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC拦截器的工作原理 2.源码分析 进入SpringMVC核心类DispatcherServlet的doDispatch方法,在SpringMVC源码阅读:核心分发器DispatcherServlet曾经分析过,这里再分析一遍 936行获得HandlerExec

SpringMVC源码阅读:视图解析器

1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何完成视图解析的 2.源码分析 在SpringMVC源码阅读:拦截器分析过doDispatch的运行过程,这里再分析一遍 回到DispatcherServlet类的doDispatch方法,看看doDispatch如何获取ModelAndView Hand

SpringMVC源码阅读:属性编辑器、数据绑定

1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何通过类型转换完成数据绑定和属性编辑器的原理,并自定义属性编辑器 2.源码分析 进入RequestMappingHandlerAdapter,该类支持参数解析和数据返回,进入invokeHandlerMethod方法 794行构造WebDataBinder

springmVC源码分析之拦截器

一个东西用久了,自然就会从仅使用的层面上升到探究其原理的层面,在javaweb中springmvc更是如此,越是优秀的框架,其底层实现代码更是复杂,而在我看来,一个优秀程序猿就相当于一名武林高手,不断进阶武功秘籍,越是高深莫测的功夫,越是要探究其原理,而springmvc就是一本十分深奥的武功秘籍. 说起拦截器,说不得不和过滤器进行对比,在此贴图一张不进行多加解释,简单的来说拦截器能作用于controller层方法实现的前后. 在这里先列出一个简单的controller层的实现 正常访问之后我们

SpringMVC源码阅读(一)

DispatcherServlet是整个SPringMVC初始化和处理请求的重要类,作为一个servlet,用友init  service destroy 方法 <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 可以自定义s

SpringMVC源码阅读(二)

今天分析下ViewResolver和View的实现  下面是ModelAndView的实现 package org.springframework.web.servlet; import java.util.Map; import org.springframework.ui.ModelMap; import org.springframework.util.CollectionUtils; public class ModelAndView { /** View instance or vie

SpringMVC源码阅读(三)

先理一下Bean的初始化路线 org.springframework.beans.factory.support.AbstractBeanDefinitionReader public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int c

Spring Framework及SpringMVC源码阅读1

web应用的启动过程 熟悉的web应用都带有一个web.xml配置文件,web应用的启动就是从这个web.xml开始的.web应用放入servlet容器如tomcat中,tomcat启动时加载web.xml.web.xml中有四个常用的配置项: listener 经常被叫做监听器 context-param filter 经常被叫做过滤器 servlet servlet将接收到的http请求交给对应的servlet处理 启动过程时这样的: 首先创建web.xml中配置的监听器,监听容器中发生的事

SpringMVC源码阅读HandlerAdapter-RequestMappingHandlerAdapter(七)

接口 public interface HandlerAdapter { /** * 是否能处理指定Handler * @param var1 * @return */ boolean supports(Object var1); /** * 处理Handler * @param var1 * @param var2 * @param var3 * @return * @throws Exception */ @Nullable ModelAndView handle(HttpServletRe