SpringMVC异常处理机制
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); } }