>>分享流行的Java框架以及开源软件,对孙卫琴的《精通Spring:Java Web开发技术详解》提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 18614 个阅读者 刷新本主题
 * 贴子主题:  Spring MVC异常处理机制 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2020-08-05 10:00:44     消息  查看  搜索  好友  邮件  复制  引用

1、原理

springmvc 通过HandlerExceptionResolver 接口来进行异常处理。任何实现该接口的类通过配置即可进行异常处理。

HandlerExceptionResolver的resolveException方法用来处理异常。接口如下:

ModelAndView resolveException(
        HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

使用

使用方法包括@ExceptionHandler和自己实现HandlerExceptionResolver接口。这里不做详细说明,请自行查阅资料。
那么这2中方法有什么区别?

自己实现HandlerExceptionResolver是自己处理异常(废话)。

@ExceptionHandler使用spring自带的类ExceptionHandlerExceptionResolver来处理异常。(具体如何处理后面详细说)

spring mvc 异常处理类介绍

到这里,我们先来暂停一下,先来了解spring mvc自身的异常处理类,这样才能继续深入。

1. HandlerExceptionResolver接口
SpringMVC异常处理核心接口。最开始所说的基础类,所有异常处理类都要实现该类。

2. AbstractHandlerExceptionResolver抽象类
实现HandlerExceptionResolver接口的抽象类。
其中属性order默认为最低优先级(int最大值),order代表整个exception handler链处理顺序,值越小,处理顺序越靠前。

    private int order = Ordered.LOWEST_PRECEDENCE;

   **//异常处理方法**
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
        Object handler, Exception ex) {
            // shouldApplyTo方法是用来判断当前exceptionHandler能否处理handler(handler是抛出异常的类或者方法)
    if (shouldApplyTo(request, handler)) {
        // Log exception, both at debug log level and at warn level, if desired.
        if (logger.isDebugEnabled()) {
            logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
        }
        logException(ex, request);
        prepareResponse(ex, response);
           ** //最终调用doResolveException抽象方法,需要子类实现**
        return doResolveException(request, response, handler, ex);
    }
    else {
        return null;
    }
}

3. AbstractHandlerMethodExceptionResolver抽象类

继承AbstractHandlerExceptionResolver抽象类的抽象类。 该类主要就是为HandlerMethod类服务,既handler参数是HandlerMethod类型。

该类重写了shouldApplyTo方法:主要目的是将handlerMethod转换为handler

    protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
    if (handler == null) {
        return super.shouldApplyTo(request, handler);
    }
    else if (handler instanceof HandlerMethod) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        handler = handlerMethod.getBean();
        return super.shouldApplyTo(request, handler);
    }
    else {
        return false;
    }
}

4. ExceptionHandlerExceptionResolver类
继承自AbstractHandlerMethodExceptionResolver,该类主要处理Controller中用@ExceptionHandler注解定义的方法。这个类也是上面所说的2中实现方式之一的处理类。

但是我不想过于深入源码,我觉得这样会导致文章过长反而不利于轻松的理解更加重要的东西,只做一些简单介绍。
我希望未来自己忘记整个内容的时候,能在几分钟内对整个exception的处理有个整体理解,并对一些重要的注意事项进行记录。
无论如何还是要看一下最重要的方法doResolveHandlerMethodException:

@Override
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
             //*** getExceptionHandlerMethod方法封装了如何寻找@exception注解的内容。 ***
    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
    if (exceptionHandlerMethod == null) {
        return null;
    }

    exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();

    try {
        if (logger.isDebugEnabled()) {
            logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
        }
                  //该处即是交给异常处理方法进行处理。
        exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception);
    }
    catch (Exception invocationEx) {
        if (logger.isErrorEnabled()) {
            logger.error("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
        }
        return null;
    }

    if (mavContainer.isRequestHandled()) {
        return new ModelAndView();
    }
    else {
        ModelAndView mav = new ModelAndView().addAllObjects(mavContainer.getModel());
        mav.setViewName(mavContainer.getViewName());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        return mav;
    }
}

该方法第一行 getExceptionHandlerMethod(handlerMethod, exception);封装了获取处理handler的异常处理方法(这里指的是@ExceptionHandler注解的方法)。
该方法中包括很多内容,缓存,验证等信息。
这里的缓存指的handler对应的异常处理resolver的mapping;

验证会验证一个handler中的相同@Exceptionhandler是否出现了多次,如果出现多次会抛出

        throw new IllegalStateException(
                "Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" +
                oldMethod + ", " + method + "}.");

简单说就是一个controller中针对一个异常类型,只能使用一个@Exceptionhandler,当然如果处理的异常类型有多个,则可以有多个。
比如下面将不合法,应为@ExceptionHandler(Exception.class)有2个,如果将其中一个@ExceptionHandler(Exception.class)修改为其他则可行。

@ExceptionHandler(Exception.class)
@ResponseBody
public CMCCApiResponse processMethod(Exception ex, HttpServletRequest request, HttpServletResponse response) {
    CMCCApiResponse cmccApiResponse = new CMCCApiResponse();
    cmccApiResponse.setCode(CMCCBaseResultCodeConstant.FAIL);
    logger.error(request.getRequestURL() + "?" + request.getQueryString());
    logger.error("ApiBaseController:ExceptionHandler:", ex);
    if (!EcovacsValidateException.class.isInstance(ex)) {
        //内部异常
        cmccApiResponse.setMsg("消息接收异常");
    } else {

        cmccApiResponse.setMsg(ex.getMessage());
    }
    return cmccApiResponse;
}

@ExceptionHandler(Exception.class)
@ResponseBody
public CMCCApiResponse processMethod2(Exception ex, HttpServletRequest request, HttpServletResponse response) {
    CMCCApiResponse cmccApiResponse = new CMCCApiResponse();
    cmccApiResponse.setCode(CMCCBaseResultCodeConstant.FAIL);
    logger.error(request.getRequestURL() + "?" + request.getQueryString());
    logger.error("ApiBaseController:ExceptionHandler:", ex);
    if (!EcovacsValidateException.class.isInstance(ex)) {
        //内部异常
        cmccApiResponse.setMsg("消息接收异常");
    } else {

        cmccApiResponse.setMsg(ex.getMessage());
    }
    return cmccApiResponse;
}

5. DefaultHandlerExceptionResolver类
继承自AbstractHandlerExceptionResolver抽象类。该类封装了对很多不同异常的处理。列举部分:

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

    try {
        if (ex instanceof NoSuchRequestHandlingMethodException) {
            return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
                    handler);
        }
        else if (ex instanceof HttpRequestMethodNotSupportedException) {
            return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
                    response, handler);
        }
        else if (ex instanceof HttpMediaTypeNotSupportedException) {
            return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
                    handler);
        }
        else if (ex instanceof HttpMediaTypeNotAcceptableException) {
            return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,
                    handler);
        }

6. ResponseStatusExceptionResolver类

继承自AbstractHandlerExceptionResolver抽象类。该类的doResolveException方法主要在异常及异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理。这一点和@ExceptionHandler类似.

7. SimpleMappingExceptionResolver类
继承自AbstractHandlerExceptionResolver抽象类,是一个简单的处理,试图映射的类。很多人会继承该类来实现自己的异常处理handler。

8. @ResponseStatus注解
让1个方法或异常有状态码(status)和理由(reason)返回。这个状态码是http响应的状态码。

异常处理的生命周期(关键之处)

上面介绍了一些基础的类,但是依然有疑惑。为何配置了@ExceptionHandler注解就可以进行异常处理?
虽然我们知道了ExceptionHandlerExceptionResolver类对配置了@ExceptionHandler注解的类进行处理,但是这是如何发生的?或者说ExceptionHandlerExceptionResolver在何时初始化(配置),何时调用?

答案是:<annotation-driven/>
我们看下<annotation-driven/>解析类AnnotationDrivenBeanDefinitionParser中部分代码片段:

    RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
    exceptionHandlerExceptionResolver.setSource(source);
    exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
    exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
    exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
    addResponseBodyAdvice(exceptionHandlerExceptionResolver);

    String methodExceptionResolverName =
            parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);

    RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
    responseStatusExceptionResolver.setSource(source);
    responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    responseStatusExceptionResolver.getPropertyValues().add("order", 1);
    String responseStatusExceptionResolverName =
            parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);

    RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
    defaultExceptionResolver.setSource(source);
    defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    defaultExceptionResolver.getPropertyValues().add("order", 2);
    String defaultExceptionResolverName =

上面代码中定义了3个异常处理类及其顺序。所以答案在这里得到解决。

顺序order

如果你的项目中有时候出现抛出异常,但是无法进入自定义的异常处理类的时候,就需要当心这个陷阱。

上面代码中定义了异常处理的默认顺序,所以如果我们自己实现自己的异常处理类的时候需要注意自己的order属性,如果不设置默认将会是排在末尾,配置多个将会依次根据顺序排序。

这里需要注意的是:如果希望自己优先处理所有异常,应该将order设置为-1,并且我也推荐使用。应为如果不讲自己的异常处理类设置为最优先的话,我们将对一些异常无法处理。比如封装spring mvc属性的时候出现类型不匹配,jsp中的异常,或者其他异常(这里记得DefaultHandlerExceptionResolver的处理吗?这个类封装了很多异常处理)。所以要当心当出现异常的时候,自己的异常处理类,无法接到任何异常信息。这一点笔者接触的一些公司经常会犯这样的错误,导致一些异常难以排查(应为线上日志几乎不会出现DEBUG模式,而JSP的一些异常却是需要DEBUG才会打印日志)。

异常处理类的调用
观察spring mvc的核心类 DispatcherServlet
整个流程如下:
doService()调用doDispatch->processDispatchResult->processHandlerException。

dodispatch如下(这类忽略掉了其他代码):

   try{
            xxxxxxxxx
    }catch (Exception ex) {
            dispatchException = ex;
    }
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

        processHandlerException 通过循环来交给异常处理类处理:

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;
        }
    }

异常处理类中如果抛出异常

这个问题很有趣,如果处理异常的方法自己抛出异常,将会如何处理?这里不妨脑洞一些,如果抛出的异常依然可以被异常处理链路来处理,那么就会出现死循环!!!那么如果不处理,这里抛出的异常该怎么办呢?
结论是:如果抛出异常,将会沿着调用栈向上抛,最终抛出到servlet进行处理,然后抛出servlet异常。也就是会500。

    try {
        doService(request, response);
    }
    catch (ServletException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

2、总结

本文讲解了spring mvc的异常处理流程。重要的地方发包括:
1、spring mvc 通过<annotation-driven/>默认加入了3个异常处理器。
2、@ExceptionHandler注解由ExceptionHandlerExceptionResolver类来处理。
3、同一个类中不能使用@ExceptionHandler注解解释相同异常2次。并且@Order注解毫无意义,应为@ExceptionHandler不是一个异常处理器,只是让异常处理器能识别到应该如何处理。
4、通过配置文件可以配置多个异常处理器,处理顺序根据order来设置,如果要自己处理所有异常,需要将order设置成小于0。并且强烈推荐如此做。否则一些异常会被spring mvc自身处理,而达不到想要的效果。
5、异常处理器中抛出的异常,将会一直往上抛,直到servlet捕获并处理,最终抛出ServletException,或者其他Exception。其实只有3中ServletException exception 和NestedServletException。




程序猿的技术大观园:www.javathinker.net
  Java面向对象编程-->集合(上)
  JavaWeb开发-->Servlet技术详解(Ⅱ)
  JSP与Hibernate开发-->Spring、JPA与Hibernate的整合
  Java网络编程-->对象的序列化与反序列化
  精通Spring-->Vue指令
  Vue3开发-->Vue Router路由管理器
  使用IntelliJ IDEA开发Maven HelloWorld
  如何编写优雅的Spring架构API
  微服务架构模型
  卫琴姐姐制作的最新视频课程:基于Spring的JavaWeb开发技术详...
  Spring MVC中自定义国家化LocaleResolver
  Spring MVC数据验证
  Spring MVC的国际化
  Nginx安装及配置
  探讨通过Feign配合Hystrix进行调用时异常的处理
  RESTful架构和RESTful API设计总结
  Spring Security中使用的责任链模式
  Spring Cloud 服务发布与调用
  阿里面试官问我:如何用Redis设计秒杀系统?我的回答让他比起...
  MyBatis解析和运行原理
  Hibernate拦截器与监听器
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。