上一篇文章 Spring Boot系列十 Spring MVC全局异常处理总结 介绍了如何在Spring MVC中实现对异常的处理,本文从源码角度理解Spring MVC异常处理原理,主要包括如下内容:
HandlerExceptionResolver是一个接口,用于处理网络请求过程中抛出的异常,但是不处理异常本身抛出的异常和视图解析过程中抛出的异常
下图是Spring MVC默认实现的HandlerExceptionResolver类
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,按照优先级排列如下:
下面详细介绍这3个HandlerExceptionResolver的作用
使用@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; }
使用@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(); }
默认的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(); }
每个具体的HandlerExceptionResolver都会实现Ordered接口,来定义执行的顺序,order值越小,越是优先执行。
如果要实现自己HandlerExceptionResolver,只需要满足两个条件:
Spring mvc启动时,初始化所有HandlerExceptionResolver到Spring 容器中
在Spring boot在启动时,会初始化WebMvcConfigurationSupport 里配置的Bean, 会创建HandlerExceptionResolverComposite对象,此对象包括3个HandlerExceptionResolver,当他捕获异常时,会依次使用这3个HandlerExceptionResolver进行处理,详细如下:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { … @Bean public HandlerExceptionResolver handlerExceptionResolver() { List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>(); configureHandlerExceptionResolvers(exceptionResolvers); if (exceptionResolvers.isEmpty()) { // 添加默认HandlerExceptionResolver类 addDefaultHandlerExceptionResolvers(exceptionResolvers); } extendHandlerExceptionResolvers(exceptionResolvers); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); return composite; } // 添加默认HandlerExceptionResolverComposite及注册到此对象中的HandlerExceptionResolverComposite protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { // 创建 ExceptionHandlerExceptionResolver() ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver(); exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager()); exceptionHandlerResolver.setMessageConverters(getMessageConverters()); exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers()); exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { exceptionHandlerResolver.setResponseBodyAdvice( Collections.<ResponseBodyAdvice<?>>singletonList(new JsonViewResponseBodyAdvice())); } exceptionHandlerResolver.setApplicationContext(this.applicationContext); exceptionHandlerResolver.afterPropertiesSet(); exceptionResolvers.add(exceptionHandlerResolver); // 创建ResponseStatusExceptionResolver ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver(); responseStatusResolver.setMessageSource(this.applicationContext); exceptionResolvers.add(responseStatusResolver); // 创建DefaultHandlerExceptionResolver exceptionResolvers.add(new DefaultHandlerExceptionResolver()); } protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() { return new ExceptionHandlerExceptionResolver(); } }
入口类,是一个Servlet,是所有请求的分发点
以下源码都在此类
DispatcherServlet在初始化时会触发onRefresh()方法,此方法会调用initStrategies方法,完成整个DispatcherServlet的初始化工作,其中initHandlerExceptionResolvers()会初始化HandlerExceptionResolvers对象
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); // 初始化HandlerExceptionResolvers对象 initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
从Spring容器的ApplicationContext中找出所有HandlerExceptionResolvers对象,将保存到对象属性handlerExceptionResolvers 中。
从这里我们也知道如果要在spring mvc中插入自己的HandlerExceptionResolver也比较简单,只需要类实现接口HandlerExceptionResolver和Ordered,使用类似@Component 的注解注解此类即可
private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) { // 从Spring容器的ApplicationContext中找出所有HandlerExceptionResolvers对象 Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (!matchingBeans.isEmpty()) { // 将Map转化为List,保存到属性handlerExceptionResolvers 中 this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values()); // 对HandlerExceptionResolvers使用据order接口里值进行排序 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); } } ...
到此初始化工作完成
当执行@RequestMapping抛出异常,会进入异常处理流程
所有的doPost, doGet等do*的方法都会执行到以下方法:找到真正业务的处理逻辑,并进行处理。
下面的代码是找到本次请求真正要处理的HandlerAdapter 对象,并进行处理,最后调用processDispatchResult对结果进行处理,这是我们关心的内容
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { … try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } // 处理最后的方法 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } … }
处理业务执行的结果,处理结束可能是 ModelAndView,也可能是Exception。如果结果是Exception,就需要通过本文提到的HandlerExceptionResolver转化为ModelAndView。然后根据ModelAndView将结果返回给请求方
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(); } else { // 如果返回值是异常,通过本文提到的HandlerExceptionResolver转化为ModelAndView Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? // 根据ModelAndView执行后续操作 if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } … }
当异常发生时, DispatcherServlet会轮询调用HandlerExceptionResolver,直到异常被转化为ModelAndView
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; } } …. }
此时异常处理完毕