Spring Boot系列十一 从源码的角度理解Spring MVC异常处理原理

1. 概述

上一篇文章Spring Boot系列十 Spring MVC全局异常处理总结介绍了如何在Spring MVC中实现对异常的处理,本文从源码角度理解Spring MVC异常处理原理,主要包括如下内容:

  • HandlerExceptionResolver以及常用实现类,理解默认实现HandlerExceptionResolver的用处和源码解读
  • Ordered接口及如何自定义HandlerExceptionResolver
  • Spring MVC 异常处理HandlerExceptionResolver对象初始化和处理流程的源码解读

2. HandlerExceptionResolver以及常用实现类

2.1 HandlerExceptionResolver接口

HandlerExceptionResolver是一个接口,用于处理网络请求过程中抛出的异常,但是不处理异常本身抛出的异常和视图解析过程中抛出的异常

下图是Spring MVC默认实现的HandlerExceptionResolver类
《Spring Boot系列十一 从源码的角度理解Spring MVC异常处理原理》

2.2. HandlerExceptionResolverComposite

Spring Boot启动时会默认注册HandlerExceptionResolverComposite对象。此类只是一个组合类,并不进行真正的异常处理。当他捕获异常时他只是将异常轮询委托给注册到它属性里的上的HandlerExceptionResolver类来处理异常,如果处理的结果不为null,则转给下一个处理

@Override
public ModelAndView resolveException(HttpServletRequest request,
     HttpServletResponse response,
     Object handler,
     Exception ex) {
    if (resolvers != null) {
        for (HandlerExceptionResolver handlerExceptionResolver : resolvers) {
            ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (mav != null) {
                return mav;
            }
        }
    }
    return null;
}

默认注册到HandlerExceptionResolverComposite 的属性有以下3个HandlerExceptionResolver,按照优先级排列如下:

  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver

下面详细介绍这3个HandlerExceptionResolver的作用

2.3. ExceptionHandlerExceptionResolver

使用@ExceptionHandler注解方法处理异常类,我们之前介绍的使用注解处理异常就有这个类的功劳。默认情况下,这个HandlerExceptionResolver的优先级是最高。
以下是ExceptionHandlerExceptionResolver运行时属性值
《Spring Boot系列十一 从源码的角度理解Spring MVC异常处理原理》
属性exceptionHandlerAdviceCache :存储@Controller里@ExceptionHandler的方法
属性exceptionHandlerAdviceCache:存储@ControllerAdvice里@ExceptionHandler的全局方法

处理异常的关键代码
入口doResolveHandlerMethodException方法会通过 getExceptionHandlerMethod获取对应的@ExceptionHandler方法,如果有找到则执行此方法

    @Override
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
        // 杳找对应的方法@ExceptionHandler
        ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
        if (exceptionHandlerMethod == null) {
            return null;
        }
....
            if (cause != null) {
                // 执行异常处理方法
                exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
            }
            else {
                // 执行异常处理方法
                exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
            }
        }
....
    }

getExceptionHandlerMethod方法:查找特定异常的@ExceptionHandler方法,首先从抛出异常的@Controller类中寻找对应的处理方法,如果没有再从@ControllerAdvice中查找全局的@ExceptionHandler方法,如果找到,则调用这个方法执行处理,否则返回null

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
    Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);

    if (handlerMethod != null) {
        // 从抛出异常的@Controller类中自身中寻找对应的处理方法,如果有找到先缓存
        ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
        if (resolver == null) {
            resolver = new ExceptionHandlerMethodResolver(handlerType);
            this.exceptionHandlerCache.put(handlerType, resolver);
        }
        Method method = resolver.resolveMethod(exception);
        if (method != null) {
            // 调用方法,返回结果
            return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
        }
    }
    // 如果没有再从@ControllerAdvice中查找全局的@ExceptionHandler方法,如果找到,则调用这个方法执行处理
    for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
        if (entry.getKey().isApplicableToBeanType(handlerType)) {
            ExceptionHandlerMethodResolver resolver = entry.getValue();
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
            }
        }
    }

    return null;
}

2.4. ResponseStatusExceptionResolver

使用@ResponseStatus处理异常,将异常转化对应的HTTP的状态码。@ResponseStatus可以定义在Excpetion的子类的类上,也可以定义在被@ExceptionHandler注解的方法上(不过这个需要小心使用,由于ExceptionHandlerExceptionResolver的优先级高,这种方式可能被ExceptionHandlerExceptionResolver覆盖掉)

异常处理入口doResolveException方法会先查找异常上的@ResponseStatus注解信息,如果有ResponseStatus ,则按照ResponseStatus 配置的值处理

// 
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
    Object handler, Exception ex) {
    // 获取异常的@ResponseStatus注解信息
    ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
    if (responseStatus != null) {
        try {
            // 如果有ResponseStatus ,则按照ResponseStatus 配置的值处理
            return resolveResponseStatus(responseStatus, request, response, handler, ex);
        }
        catch (Exception resolveEx) {
            logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);
        }
    }
    else if (ex.getCause() instanceof Exception) {
        …
    }
    return null;
}

根据ResponseStatus 的值设置返回的http状态码和原因

protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
    HttpServletResponse response, Object handler, Exception ex) throws Exception {

    int statusCode = responseStatus.code().value();
    String reason = responseStatus.reason();
    if (!StringUtils.hasLength(reason)) {
               // 设置返回的http状态码
        response.sendError(statusCode);
    }
    else {
        String resolvedReason = (this.messageSource != null ?
            this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
            reason);
        // 设置返回的http状态码和原因
        response.sendError(statusCode, resolvedReason);
    }
    return new ModelAndView();
    }

2.5. DefaultHandlerExceptionResolver

默认的HandlerExceptionResolver,将特定异常转化为标准的HTTP的状态码。
详细如下:左边是异常名称,右边是http的状态码
《Spring Boot系列十一 从源码的角度理解Spring MVC异常处理原理》

通过代码解释此类行为, 只列出NoSuchRequestHandlingMethodException相关的转换http错误码的代码,表格里其他异常处理类似

异常处理入口doResolveException方法,如果发现异常是NoSuchRequestHandlingMethodException,则调用方法handleNoSuchRequestHandlingMethod

 // 对于NoSuchRequestHandlingMethodException进行转化http错误大码
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
    Object handler, Exception ex) {
    try {
        if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
            return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex,
                request, response, handler);
        }
        else if (ex instanceof HttpRequestMethodNotSupportedException) {
            …
        }else if 
        …
}

handleNoSuchRequestHandlingMethod方法返回404错误码和错误信息

protected ModelAndView handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,
    HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

    pageNotFoundLogger.warn(ex.getMessage());
    response.sendError(HttpServletResponse.SC_NOT_FOUND);
    return new ModelAndView();
    }

2.6. Ordered和实现自定义HandlerExceptionResolver类

每个具体的HandlerExceptionResolver都会实现Ordered接口,来定义执行的顺序,order值越小,越是优先执行。
如果要实现自己HandlerExceptionResolver,只需要满足两个条件:

3. Spring MVC异常处理源码解读

3.1. Spring MVC启动时如何初始化HandlerExceptionResolver

Spring mvc启动时,初始化所有HandlerExceptionResolver到Spring 容器中
在Spring boot在启动时,会初始化WebMvcConfigurationSupport 里配置的Bean, 会创建HandlerExceptionResolverComposite对象,此对象包括3个HandlerExceptionResolver,当他捕获异常时,会依次使用这3个HandlerExceptionResolver进行处理,详细如下:

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {@Bean
    public HandlerExceptionResolver handlerExceptionResolver() {
        List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>();
        configureHandlerExceptionResolvers(exceptionResolvers);
        if (exceptionResolvers.isEmpty()) {
        // 添加默认HandlerExceptionResolver类 
        addDefaultHandlerExceptionResolvers(exceptionResolvers);
        }
        extendHandlerExceptionResolvers(exceptionResolvers);
        HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
        composite.setOrder(0);
        composite.setExceptionResolvers(exceptionResolvers);
        return composite;
    }

    // 添加默认HandlerExceptionResolverComposite及注册到此对象中的HandlerExceptionResolverComposite
    protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // 创建 ExceptionHandlerExceptionResolver()
        ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
        exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
        exceptionHandlerResolver.setMessageConverters(getMessageConverters());
        exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
        exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
        if (jackson2Present) {
            exceptionHandlerResolver.setResponseBodyAdvice(
                Collections.<ResponseBodyAdvice<?>>singletonList(new JsonViewResponseBodyAdvice()));
        }
        exceptionHandlerResolver.setApplicationContext(this.applicationContext);
        exceptionHandlerResolver.afterPropertiesSet();
        exceptionResolvers.add(exceptionHandlerResolver);
         // 创建ResponseStatusExceptionResolver 
    ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
    responseStatusResolver.setMessageSource(this.applicationContext);
    exceptionResolvers.add(responseStatusResolver);
    // 创建DefaultHandlerExceptionResolver
    exceptionResolvers.add(new DefaultHandlerExceptionResolver());
    }

    protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
        return new ExceptionHandlerExceptionResolver();
        }


}

3.2. DispatcherServlet

入口类,是一个Servlet,是所有请求的分发点
以下源码都在此类

3.2.1. 初始化

DispatcherServlet在初始化时会触发onRefresh()方法,此方法会调用initStrategies方法,完成整个DispatcherServlet的初始化工作,其中initHandlerExceptionResolvers()会初始化HandlerExceptionResolvers对象

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    // 初始化HandlerExceptionResolvers对象
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

从Spring容器的ApplicationContext中找出所有HandlerExceptionResolvers对象,将保存到对象属性handlerExceptionResolvers 中。
从这里我们也知道如果要在spring mvc中插入自己的HandlerExceptionResolver也比较简单,只需要类实现接口HandlerExceptionResolver和Ordered,使用类似@Component 的注解注解此类即可

private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;

    if (this.detectAllHandlerExceptionResolvers) {
        // 从Spring容器的ApplicationContext中找出所有HandlerExceptionResolvers对象
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
            .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            // 将Map转化为List,保存到属性handlerExceptionResolvers 中
            this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
            // 对HandlerExceptionResolvers使用据order接口里值进行排序
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
}
...

到此初始化工作完成

3.2.2. 异常处理流程

当执行@RequestMapping抛出异常,会进入异常处理流程

所有的doPost, doGet等do*的方法都会执行到以下方法:找到真正业务的处理逻辑,并进行处理。

下面的代码是找到本次请求真正要处理的HandlerAdapter 对象,并进行处理,最后调用processDispatchResult对结果进行处理,这是我们关心的内容

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

…
try {

    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
        processedRequest = checkMultipart(request);
        multipartRequestParsed = (processedRequest != request);

    // Determine handler for the current request.
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null || mappedHandler.getHandler() == null) {
        noHandlerFound(processedRequest, response);
        return;
    }

    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // Process last-modified header, if supported by the handler.
    String method = request.getMethod();
    boolean isGet = "GET".equals(method);
    if (isGet || "HEAD".equals(method)) {
        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
        if (logger.isDebugEnabled()) {
            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
        }
        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
            return;
        }
    }

    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }

    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    if (asyncManager.isConcurrentHandlingStarted()) {
        return;
    }

    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
        dispatchException = ex;
    }
    catch (Throwable err) {
        // As of 4.3, we're processing Errors thrown from handler methods as well,
        // making them available for @ExceptionHandler methods and other scenarios.
        dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    // 处理最后的方法
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
…
}

处理业务执行的结果,处理结束可能是 ModelAndView,也可能是Exception。如果结果是Exception,就需要通过本文提到的HandlerExceptionResolver转化为ModelAndView。然后根据ModelAndView将结果返回给请求方

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            // 如果返回值是异常,通过本文提到的HandlerExceptionResolver转化为ModelAndView
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    // 根据ModelAndView执行后续操作
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    …
}

当异常发生时, DispatcherServlet会轮询调用HandlerExceptionResolver,直到异常被转化为ModelAndView

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    Object handler, Exception ex) throws Exception {

    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
            break;
        }
    }
    ….
}

此时异常处理完毕

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/hry2015/article/details/78957949
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞