异常是避免不了的的。我们能做的就是减少异常, 以及做好异常的处理。
本文说说SpringMVC中的异常处理。
1.场景分析
我们在写业务代码时,多多少少会自发的做一些异常的捕获,或者处理。
这里有两个问题:
- 你不可能处理到大多数异常
- 大量的try catch代码充斥在业务代码中
2.Servlet+tomcat时代
在Servlet+tomcat时代, 异常的统一处理通过在web.xml中配置的。
<!-- 根据状态码 --> <error-page> <error-code>500</error-code> <location>/500.jsp</location> </error-page> <!-- 根据异常类型 --> <error-page> <exception-type>java.lang.RuntimeException</exception-type> <location>/500.jsp</location> </error-page>
3.springmvc时代
在SpringMVC时代,得意于SpringMVC的中心化思想,所有请求走DispatcherServlet
分发器。在DispatcherServlet
里可以做一个统一的异常捕获与处理。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { 分发请求,去调用业务逻辑 } catch (Exception ex) { dispatchException = ex;//捕获异常 } catch (Throwable err) {//捕获错误 dispatchException = new NestedServletException("Handler dispatch failed", err); } //结果处理或者异常处理 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
处理流程:
- 当业务代码抛出异常时,被捕获后,赋值给一个
dispatchException
变量。然后调用processDispatchResult
去处理 - 在
processDispatchResult
中,异常的处理是调用processHandlerException(request, response, handler, exception)
方法。此方法内会遍历所有的异常处理器
,找到一个可以处理当前异常的异常处理器
。
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); if (exMv != null) { break; } } }
可以看到异常的处理是由SpringMVC的一个组件HandlerExceptionResolver
来处理的。
这就引出了本文的中心,异常处理器HandlerExceptionResolver
4.核心HandlerExceptionResolver
此组件只专门用来处理异常的组件,
HandlerExceptionResolver
接口只有一个异常解析的方法定义。
ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
springMVC为其提供了多个实现
这里主要的是AbstractHandlerExceptionResolver
分支下,是处理异常的各个实现类:
AbstractHandlerExceptionResolver:
此抽象类,不但实现了resolveException
方法,而且还提供了额外的方法
- shouldApplyTo : 匹配逻辑,用于判断当前异常处理器支持那种类型的异常处理
4.1分类
- SimpleMappingExceptionResolver:简单映射,异常处理。根据异常类型匹配到一个视图处理
- ResponseStatusExceptionResolver:若抛出的异常类型上有
@ResponseStatus
注解,此处理器会处理,根据注解的内容,返回相应的HTTP Status Code和内容给客户端【@ResponseStatus标注在异常类上此处理器才会处理】 - AnnotationMethodHandlerExceptionResolver和ExceptionHandlerExceptionResolver:用来支持ExceptionHandler注解,使用被ExceptionHandler注解所标记的方法来处理异常。其中AnnotationMethodHandlerExceptionResolver在3.0版本中开始提供,ExceptionHandlerExceptionResolver在3.1版本中开始提供,从3.2版本开始,Spring推荐使用ExceptionHandlerExceptionResolver。
高阶用法
- DefaultHandlerExceptionResolver:默认的异常处理器,能处理springMVC大部分异常,也是springmvc默认装配的异常处理器。
最常用
4.2异常处理器的初始化
异常的初始化伴随着DispatcherServlet
的初始化
DispatcherServlet protected void initStrategies(ApplicationContext context) { 。。。 initHandlerExceptionResolvers(context); 。。。 }
异常初始化策略:
(1. detectAllHandlerExceptionResolvers 为true时从容器内获取所有实现了ExceptionResolver接口的bean,并根据其order属性排序,依次调用
(2.detectAllHandlerExceptionResolvers=false时,只获取handlerExceptionResolver的bean作为异常处理器
(3.上属两种还找不到异常处理器的情况下,默认加载配置文件中的处理器
默认的异常处理bean定义在DispatcherServlet.properties
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver