专栏名称: hryou0922
目录
相关文章推荐
贵阳日报  ·  事关贵安新区!贵州省人民政府批复同意 ·  11 小时前  
贵阳日报  ·  事关贵安新区!贵州省人民政府批复同意 ·  11 小时前  
贵州日报  ·  颜宁有新职 ·  昨天  
51好读  ›  专栏  ›  hryou0922

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

hryou0922  · 掘金  ·  · 2018-01-23 06:18

正文

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类
这里写图片描述

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运行时属性值
这里写图片描述
属性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的状态码
这里写图片描述

通过代码解释此类行为, 只列出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,只需要满足两个条件:







请到「今天看啥」查看全文