SpringMVC異常處理機制
springMVC會將所有在doDispatch方法中的異常捕獲,然后處理。無法處理的異常會拋出給容器處理。
在doDispatch()中調用processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)處理結果:包括出現和不出現異常的處理都放在這里面
下面是它的源碼
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();//若拋出的異常是人為的指定mv的,則直接執行 } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling"); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
如果有異常出現,首先判斷是不是ModelAndViewDefiningException,這個異常是認為的定義的,指定了modelAndView,所以直接拿到該mv,執行就行。
如果不是ModelAndViewDefiningException,則要調用processHandlerException(request, response, handler, exception);方法處理。具體源碼如下:
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; } } if (exMv != null) { if (exMv.isEmpty()) { request.setAttribute(EXCEPTION_ATTRIBUTE, ex); return null; } // We might still need view name translation for a plain error model... if (!exMv.hasView()) { exMv.setViewName(getDefaultViewName(request)); } if (logger.isDebugEnabled()) { logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); } WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); return exMv; } throw ex; }
該方法會遍歷所有的handlerExceptionResolvers,判斷能否處理該異常。調用各自的resolveException 方法(一般拋出的異常都需要通過這一步,要么能被處理;要么處理不了,向上拋)。
則現在需要關注這些handlerExceptionResolvers是如何得到的,有什么分類呢?具體參考http://www.cnblogs.com/fangjian0423/p/springMVC-exception-analysis.html#ExceptionHandlerExceptionResolver
1,handlerExceptionResolver的結構如下圖所示
2,類的分析
根據上述結構,對SpringMVC異常處理類進行分析:
l SimpleMappingException:根據配置進行解析異常的類,包括配置異常類型,默認的錯誤視圖,默認的響應碼,異常映射等配置屬性。
使用時只需要在xml配置文件中配置一下就可以
l < mvc:annotation-driven />配置中定義的HandlerExceptionResolver實現類,我們看下< mvc:annotation-driven />配置解析類org.springframework.web.servlet.config
.AnnotationDrivenBeanDefinitionParser中部分代碼片段:

可以看到,當我們定義< mvc:annotation-driven >時,框架會自動將三個類ExceptionHandlerExceptionResolver,ResponseStatusExceptionResolver,DefaultHandlerExceptionResolver添加到DispatcherServlet中的handlerExceptionResolvers集合中。並且order分別為1,2,3,ExceptionHandlerExceptionResolver優先級最高,ResponseStatusExceptionResolver第二,DefaultHandlerExceptionResolver第三。
下面分別解析這三個類以及如何使用
(1)ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver處理過程總結一下:根據用戶調用Controller中相應的方法得到HandlerMethod,之后構造ExceptionHandlerMethodResolver,構造ExceptionHandlerMethodResolver有2種選擇,1.通過HandlerMethod拿到Controller,找出Controller中帶有@ExceptionHandler注解的方法(局部) 2.找到@ControllerAdvice注解配置的類中的@ExceptionHandler注解的方法(全局)。這2種方式構造的ExceptionHandlerMethodResolver中都有1個key為Throwable,value為Method的緩存。之后通過發生的異常找出對應的Method,然后調用這個方法進行處理。這里異常還有個優先級的問題,比如發生的是NullPointerException,但是聲明的異常有Throwable和Exception,這時候ExceptionHandlerMethodResolver找Method的時候會根據異常的最近繼承關系找到繼承深度最淺的那個異常,即Exception。
一般使用的時候通過第二種方法,定義一個類並配上注解@ControllerAdvice。然后再該類中定義處理異常的方法並使用注解@ExceptionHandler
(2)ResponseStatusExceptionResolver
該類的doResolveException方法主要在異常及異常父類中找到@ResponseStatus注解,然后使用這個注解的屬性進行處理。
一般使用的時候,定義一個異常的時候,帶上@ResponseStatus注解,並設置響應狀態碼。
(3)DefaultHandlerExceptionResolver
該類的doResolveException方法中主要對一些特殊的異常進行處理,比如NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。
l 自定義異常處理器
實現HandlerExceptionReslover或繼承AbstractHandlerExceptionResolver,將自定義的處理器配置到xml文件中,設置order。
3,使用
l SimpleMappingException
在spring-mvc.xml中定義
可以設置order,defaultErrorView,exceptionAttribute,exceptionMappings
如下所示
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 配置order為-1,表示優先級最高 --> <property name="order" value="-1" /> <!-- 定義默認的異常處理頁面,當該異常類型的注冊時使用 --> <property name="defaultErrorView" value="error"></property> <!-- 定義異常處理頁面用來獲取異常信息的變量名,默認名為exception --> <property name="exceptionAttribute" value="ex"></property> <!-- 定義需要特殊處理的異常,用類名或完全路徑名作為key,異常也頁名作為值 --> <property name="exceptionMappings"> <props> <prop key="yellow.exception.BusinessException">error-business</prop> <prop key="yellow.exception.ParameterException">error-parameter</prop> <!-- 這里還可以繼續擴展對不同異常類型的處理 --> </props> </property> </bean>
l 使用@ControllerAdvice,@ExceptionHandler,@ResponseStatuts
@ControllerAdvice public class AControllerAdvice { @ExceptionHandler(BusinessException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public String handleException1() { return "1,處理Business異常"; } @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public String handleException() { return "2,處理一般異常"; } }
當出現BusinessException時,結果如下

可以發現,返回了400 bad request狀態碼,表示@ResponseStatus設置生效了。
l 自定義異常處理器:由於第二種方法已經很好的滿足了處理異常的需求,一般項目中就不會自定義的處理器了。使用也很簡單,只需覆蓋doResolveException方法就可以了。
如下所示:如果想返回json,可以使用return new ModelAndView(new FastJsonJsonView(), map);來實現。
public class MyExceptionHandlerWithOrder extends AbstractHandlerExceptionResolver { @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // TODO Auto-generated method stub Map<String, Object> map = new HashMap<String, Object>(); map.put("type", ex.getClass().getName()); map.put("message", ex.getMessage()); return new ModelAndView(new FastJsonJsonView(), map); } }
