該系列文檔是本人在學習 Spring MVC 的源碼過程當中總結下來的,可能對讀者不太友好,請結合個人源碼註釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀html
Spring 版本:5.2.4.RELEASEjava
該系列其餘文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》git
HandlerExceptionResolver
組件,處理器異常解析器,將處理器( handler
)執行時發生的異常(也就是處理請求,執行方法的過程當中)解析(轉換)成對應的 ModelAndView 結果github
先來回顧一下在 DispatcherServlet
中處理請求的過程當中哪裏使用到 HandlerExceptionResolver
組件,能夠回到《一個請求的旅行過程》中的 DispatcherServlet
的 processHandlerException
方法中看看,以下:web
@Nullable protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { // Success and error responses may use different content types // 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 屬性 request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); // Check registered HandlerExceptionResolvers... // <a> 遍歷 HandlerExceptionResolver 數組,解析異常,生成 ModelAndView 對象 ModelAndView exMv = null; if (this.handlerExceptionResolvers != null) { // 遍歷 HandlerExceptionResolver 數組 for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) { // 解析異常,生成 ModelAndView 對象 exMv = resolver.resolveException(request, response, handler, ex); // 生成成功,結束循環 if (exMv != null) { break; } } } // <b> 狀況一,生成了 ModelAndView 對象,進行返回 if (exMv != null) { // ModelAndView 對象爲空,則返回 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()) { String defaultViewName = getDefaultViewName(request); if (defaultViewName != null) { exMv.setViewName(defaultViewName); } } // 設置請求中的錯誤消息屬性 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); return exMv; } // <c> 狀況二,未生成 ModelAndView 對象,則拋出異常 throw ex; }
在 Spring MVC 的 DispatcherServlet
處理請求執行方法過程當中,不論是否拋出異常都會進行結果處理,若是拋出了異常也須要調用該方法處理異常spring
能夠看到,在 <a>
處會遍歷全部的 HandlerExceptionResolver
異常處理器來處理,若是某一個處理器處理成功並返回 ModelAndView 對象,則直接返回數組
org.springframework.web.servlet.HandlerExceptionResolver
,異常處理器接口,代碼以下:緩存
public interface HandlerExceptionResolver { /** * 解析異常,轉換成對應的 ModelAndView 結果 */ @Nullable ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); }
HandlerExceptionResolver 接口體系的結構以下:mvc
在 DispatcherServlet
的 initHandlerExceptionResolvers(ApplicationContext context)
方法,初始化 HandlerExceptionResolver 組件,方法以下:app
private void initHandlerExceptionResolvers(ApplicationContext context) { // 置空 handlerExceptionResolvers 處理 this.handlerExceptionResolvers = null; // 狀況一,自動掃描 HandlerExceptionResolver 類型的 Bean 們 if (this.detectAllHandlerExceptionResolvers) { // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values()); // We keep HandlerExceptionResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); } } // 狀況二,得到名字爲 HANDLER_EXCEPTION_RESOLVER_BEAN_NAME 的 Bean else { try { HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); this.handlerExceptionResolvers = Collections.singletonList(her); } catch (NoSuchBeanDefinitionException ex) { // Ignore, no HandlerExceptionResolver is fine too. } } // Ensure we have at least some HandlerExceptionResolvers, by registering // default HandlerExceptionResolvers if no other resolvers are found. /** * 狀況三,若是未得到到,則得到默認配置的 HandlerExceptionResolver 類 * {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver} * {@link org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver} * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver} */ if (this.handlerExceptionResolvers == null) { this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }
若是「開啓」探測功能,則掃描已註冊的 HandlerExceptionResolver 的 Bean 們,添加到 handlerExceptionResolvers
中,默認開啓
若是「關閉」探測功能,則得到 Bean 名稱爲 "handlerExceptionResolver" 對應的 Bean ,將其添加至 handlerExceptionResolvers
若是未得到到,則得到默認配置的 HandlerExceptionResolver 類,調用 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
方法,就是從 DispatcherServlet.properties
文件中讀取 HandlerExceptionResolver 的默認實現類,以下:
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
在 Spring Boot 中,默認配置下會走上述 1
的邏輯,handlerExceptionResolvers
有兩個元素:
org.springframework.boot.autoconfigure.web.DefaultErrorAttributes
:在 Spring Boot 中,邏輯比較簡單,暫時忽略org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
:複合的 HandlerExceptionResolver 實現類接下來會對 HandlerExceptionResolverComposite
中的這三種異常處理器進行分析
org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
,實現 HandlerExceptionResolver、Ordered 接口,複合的 HandlerExceptionResolver 實現類
public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered { /** * 異常解析器數組 */ @Nullable private List<HandlerExceptionResolver> resolvers; /** * 優先級,默認最低 */ private int order = Ordered.LOWEST_PRECEDENCE; }
resolvers
:HandlerExceptionResolver 實現類列表order
:優先級,默認最低從上面的初始化過程當中能夠看到,Spring Boot 默認配置下 HandlerExceptionResolverComposite 包含三個實現類:
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
實現 resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)
方法,遍歷 HandlerExceptionResolver 數組,逐個處理異常 ex
,若是成功,則返回 ModelAndView 對象,方法以下:
@Override @Nullable public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { if (this.resolvers != null) { for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) { ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex); if (mav != null) { return mav; } } } return null; }
org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver
,實現 HandlerExceptionResolver、Ordered 接口,HandlerExceptionResolver 抽象類,做爲全部 HandlerExceptionResolver 實現類的基類
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered { private static final String HEADER_CACHE_CONTROL = "Cache-Control"; /** * 優先級,默認最低 */ private int order = Ordered.LOWEST_PRECEDENCE; /** * 匹配的處理器對象的集合 */ @Nullable private Set<?> mappedHandlers; /** * 匹配的處理器類型的數組 */ @Nullable private Class<?>[] mappedHandlerClasses; /** * 防止響應緩存 */ private boolean preventResponseCaching = false; }
上面的這些屬性在後續方法中會講到
shouldApplyTo(HttpServletRequest request, Object handler)
方法,判斷當前 HandlerExceptionResolver 是否能應用到傳入的 handler
處理器,方法以下:
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { if (handler != null) { // <1> 若是 mappedHandlers 包含 handler 對象,則返回 true if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { return true; } // <2> 若是 mappedHandlerClasses 包含 handler 的類型,則返回 true if (this.mappedHandlerClasses != null) { for (Class<?> handlerClass : this.mappedHandlerClasses) { if (handlerClass.isInstance(handler)) { return true; } } } } // Else only apply if there are no explicit handler mappings. // <3> 若是 mappedHandlers 和 mappedHandlerClasses 都爲空,說明直接匹配 return (this.mappedHandlers == null && this.mappedHandlerClasses == null); }
mappedHandlers
包含該 handler
處理器對象,則返回 true
mappedHandlerClasses
包含該 handler
處理器所在類,則返回 true
mappedHandlers
和 mappedHandlerClasses
都爲空,說明直接匹配prepareResponse(Exception ex, HttpServletResponse response)
方法,阻止響應緩存,方法以下:
protected void prepareResponse(Exception ex, HttpServletResponse response) { if (this.preventResponseCaching) { preventCaching(response); } } /** * Prevents the response from being cached, through setting corresponding * HTTP {@code Cache-Control: no-store} header. * @param response current HTTP response */ protected void preventCaching(HttpServletResponse response) { response.addHeader(HEADER_CACHE_CONTROL, "no-store"); }
若是想要阻止響應緩存,須要設置 preventResponseCaching
爲 true
實現 resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,代碼以下:
@Override @Nullable public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { // <1> 判斷是否能夠應用 if (shouldApplyTo(request, handler)) { // <1.1> 阻止緩存 prepareResponse(ex, response); // <1.2> 執行解析異常,返回 ModelAndView 對象 ModelAndView result = doResolveException(request, response, handler, ex); // <1.3> 若是 ModelAndView 對象非空,則打印日誌 if (result != null) { // Print debug message when warn logger is not enabled. if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) { logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result)); } // Explicitly configured warn logger in logException method. logException(ex, request); } // <1.4> 返回執行結果 return result; } // <2> 不可應用,直接返回 null else { return null; } } @Nullable protected abstract ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
調用 shouldApplyTo(HttpServletRequest request, Object handler)
方法,判斷是否能夠應用,若是能夠應用
prepareResponse(Exception ex, HttpServletResponse response)
方法,阻止緩存doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)
抽象方法,執行解析異常,返回 ModelAndView 對象不可應用,直接返回 null
org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver
,繼承 AbstractHandlerExceptionResolver 抽象類,基於 handler
類型爲 HandlerMethod 的 HandlerExceptionResolver 抽象類。
可能你會有疑惑,爲何 AbstractHandlerMethodExceptionResolver 只有一個 ExceptionHandlerExceptionResolver 子類,爲何還要作抽象呢?由於 ExceptionHandlerExceptionResolver 是基於 @ExceptionHandler
註解來配置對應的異常處理器,而若是將來咱們想自定義其它的方式來配置對應的異常處理器,就能夠來繼承 AbstractHandlerMethodExceptionResolver 這個抽象類。😈
有沒發現 Spring MVC 中,存在大量的邏輯與配置分離的分層實現,嘻嘻~:happy:
重寫 shouldApplyTo(HttpServletRequest request, Object handler)
方法,代碼以下:
@Override protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { // 狀況一,若是 handler 爲空,則直接調用父方法 if (handler == null) { return super.shouldApplyTo(request, null); } // 狀況二,處理 handler 爲 HandlerMethod 類型的狀況 else if (handler instanceof HandlerMethod) { // <x> 得到真正的 handler HandlerMethod handlerMethod = (HandlerMethod) handler; handler = handlerMethod.getBean(); // 調用父方法 return super.shouldApplyTo(request, handler); } // 狀況三,直接返回 false else { return false; } }
重點在於狀況二,須要在 <x>
處,調用 HandlerMethod#getBean()
方法,得到真正的 handler
處理器。
重寫 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,代碼以下:
@Override @Nullable protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex); } @Nullable protected abstract ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);
將 handler
轉換成 HandlerMethod 類型,並提供新的抽象方法
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
,實現 ApplicationContextAware、InitializingBean 接口,繼承 AbstractHandlerMethodExceptionResolver 抽象類,基於 @ExceptionHandler
配置 HandlerMethod 的 HandlerExceptionResolver 實現類。
可能你沒有使用 @ExceptionHandler
註解來實現過異常的處理,例如:
@Log4j2 @RestControllerAdvice public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler({EmptyArgumentException.class, IllegalArgumentException.class}) public Result<?> customizeHandleArgumentException(HttpServletRequest request, final Exception e, HttpServletResponse response) { response.setStatus(HttpStatus.OK.value()); return Result.fail(ResultCode.PARAM_ERROR.getCode(), e.getMessage()); } @ExceptionHandler({Exception.class}) public Result<?> customizeHandleException(HttpServletRequest request, final Exception e, HttpServletResponse response) { log.error("異常攔截[{}]:", e.getMessage(), e); response.setStatus(HttpStatus.OK.value()); return Result.fail(ResultCode.UNKNOWN.getCode(), e.getMessage()); } }
該自定義異常處理類會處理 Controller
類拋出的指定類型的異常
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { /** * 自定義的方法參數處理器 */ @Nullable private List<HandlerMethodArgumentResolver> customArgumentResolvers; /** * 方法參數處理器組合 */ @Nullable private HandlerMethodArgumentResolverComposite argumentResolvers; /** * 自定義的執行結果處理器 */ @Nullable private List<HandlerMethodReturnValueHandler> customReturnValueHandlers; /** * 執行結果處理器組合 */ @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; /* * HTTP 消息轉換器 */ private List<HttpMessageConverter<?>> messageConverters; private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(); /** * 響應體的後置加強器 */ private final List<Object> responseBodyAdvice = new ArrayList<>(); @Nullable private ApplicationContext applicationContext; private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64); private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>(); public ExceptionHandlerExceptionResolver() { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316 // 初始化 messageConverters this.messageConverters = new ArrayList<>(); this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(stringHttpMessageConverter); try { this.messageConverters.add(new SourceHttpMessageConverter<>()); } catch (Error err) { // Ignore when no TransformerFactory implementation is available } this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); } }
有沒有一種熟悉的感受,和 《HandlerAdapter 組件(一)之 HandlerAdapter》 的 RequestMappingHandlerAdapter 相似,有大量的相同變量,例如參數解析器和返回結果處理器,最終也是調用 ServletInvocableHandlerMethod 的方法。由於你定義也是定義的方法去處理相關的異常😈 往下看
由於 ExceptionHandlerExceptionResolver 實現了 InitializingBean 接口,在 Sping 初始化該 Bean 的時候,會調用該方法,完成一些初始化工做,方法以下:
@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans // 初始化 exceptionHandlerAdviceCache、responseBodyAdvice initExceptionHandlerAdviceCache(); // 初始化 argumentResolvers 參數 if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } // 初始化 returnValueHandlers 參數 if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
initExceptionHandlerAdviceCache()
方法,初始化 exceptionHandlerAdviceCache
、responseBodyAdvice
,詳情見下文argumentResolvers
屬性。其中,#getDefaultArgumentResolvers()
方法,得到默認的 HandlerMethodArgumentResolver 數組,詳情見下文returnValueHandlers
屬性。其中,#getDefaultReturnValueHandlers()
方法,得到默認的 HandlerMethodReturnValueHandler 數組,詳情見下文initExceptionHandlerAdviceCache()
方法,初始化 exceptionHandlerAdviceCache
、responseBodyAdvice
,方法以下:
private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } // <1> 掃描 @ControllerAdvice 註解的 Bean 們,並將進行排序 List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans); // <2> 遍歷 ControllerAdviceBean 數組 for (ControllerAdviceBean adviceBean : adviceBeans) { Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } // <2.1> 掃描該 ControllerAdviceBean 對應的類型 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); // <2.2> 有 @ExceptionHandler 註解,則添加到 exceptionHandlerAdviceCache 中 if (resolver.hasExceptionMappings()) { this.exceptionHandlerAdviceCache.put(adviceBean, resolver); } // <2.3> 若是該 beanType 類型是 ResponseBodyAdvice 子類,則添加到 responseBodyAdvice 中 if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { this.responseBodyAdvice.add(adviceBean); } } if (logger.isDebugEnabled()) { int handlerSize = this.exceptionHandlerAdviceCache.size(); int adviceSize = this.responseBodyAdvice.size(); if (handlerSize == 0 && adviceSize == 0) { logger.debug("ControllerAdvice beans: none"); } else { logger.debug("ControllerAdvice beans: " + handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice"); } } }
調用 ControllerAdviceBean
的 findAnnotatedBeans(ApplicationContext context)
方法,掃描 @ControllerAdvice
註解的 Bean 們,並將進行排序,這裏就會掃描到上面示例中 CustomizeExceptionHandler 自定義異常處理類
遍歷 ControllerAdviceBean 數組
ExceptionHandlerMethodResolver
對象 resolver
,該對象在下面會分析@ExceptionHandler
註解,則將resolver
添加到 exceptionHandlerAdviceCache
中beanType
類型是 ResponseBodyAdvice 子類,則添加到 responseBodyAdvice
中getDefaultArgumentResolvers()
方法,得到默認的 HandlerMethodArgumentResolver 數組,方法以下:
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); // Annotation-based argument resolution resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } return resolvers; }
getDefaultReturnValueHandlers()
方法,得到默認的 HandlerMethodReturnValueHandler 數組,方法以下:
protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(); // Single-purpose return value types handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor( getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice)); // Annotation-based return value types handlers.add(new ModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor( getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // Custom return value types if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all handlers.add(new ModelAttributeMethodProcessor(true)); return handlers; }
在 ExceptionHandlerExceptionResolver 的
initExceptionHandlerAdviceCache
方法中會用到,二者的名字太容易混淆了
org.springframework.web.method.annotation.ExceptionHandlerMethodResolver
,添加 @ControllerAdvice
註解的 Bean,用於解析添加了 @ExceptionHandler
註解的方法
public class ExceptionHandlerMethodResolver { /** * A filter for selecting {@code @ExceptionHandler} methods. * * MethodFilter 對象,用於過濾帶有 @ExceptionHandler 註解的方法 */ public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class); /** * 已經映射的方法 * * 在 {@link #ExceptionHandlerMethodResolver(Class)} 構造方法中初始化 */ pivate final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16); /** * 已經匹配的方法 * * 在 {@link #resolveMethod(Exception)} 方法中初始化 */ private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16); public ExceptionHandlerMethodResolver(Class<?> handlerType) { // <1> 遍歷 @ExceptionHandler 註解的方法,這些方法用於處理對應的異常 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { // <2> 遍歷處理的異常集合,獲取到該方法能處理哪些異常 for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { // <3> 添加到 mappedMethods 中 addExceptionMapping(exceptionType, method); } } } }
mappedMethods
和 exceptionLookupCache
差異在於,後者是通過查找,比較優先級以後所產生的
遍歷 @ExceptionHandler
註解的方法
調用 detectExceptionMappings(Method method)
方法,得到方法的異常數組,以下:
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) { List<Class<? extends Throwable>> result = new ArrayList<>(); // 首先,從方法上的 @ExceptionHandler 註解中,得到要處理的異常類型,添加到 result 中 detectAnnotationExceptionMappings(method, result); // 其次,若是獲取不到,從方法參數中,得到所處理的異常,添加到 result 中 if (result.isEmpty()) { for (Class<?> paramType : method.getParameterTypes()) { if (Throwable.class.isAssignableFrom(paramType)) { result.add((Class<? extends Throwable>) paramType); } } } // 若是獲取不到,則拋出 IllegalStateException 異常 if (result.isEmpty()) { throw new IllegalStateException("No exception types mapped to " + method); } return result; } private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) { ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class); Assert.state(ann != null, "No ExceptionHandler annotation"); result.addAll(Arrays.asList(ann.value())); }
調用 addExceptionMapping(Class<? extends Throwable> exceptionType, Method method)
方法,添加到 mappedMethods
中,以下:
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) { // 添加到 mappedMethods 中 Method oldMethod = this.mappedMethods.put(exceptionType, method); // 若是已存在,說明衝突,因此拋出 IllegalStateException 異常 if (oldMethod != null && !oldMethod.equals(method)) { throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" + oldMethod + ", " + method + "}"); } }
hasExceptionMappings()
方法,判斷 mappedMethods
非空,方法以下:
public boolean hasExceptionMappings() { return !this.mappedMethods.isEmpty(); }
resolveMethod(Exception exception)
方法,獲取解析異常對應的方法,方法以下:
@Nullable public Method resolveMethod(Exception exception) { return resolveMethodByThrowable(exception); } @Nullable public Method resolveMethodByThrowable(Throwable exception) { // 首先,得到異常對應的方法 Method method = resolveMethodByExceptionType(exception.getClass()); // 其次,獲取不到,則使用異常 cause 對應的方法 if (method == null) { Throwable cause = exception.getCause(); if (cause != null) { method = resolveMethodByExceptionType(cause.getClass()); } } return method; }
按照 exception
和 exception.cause
的前後,調用 resolveMethodByExceptionType(Class<? extends Throwable> exceptionType)
方法,得到異常對應的方法,以下:
@Nullable public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) { // 首先,先從 exceptionLookupCache 緩存中得到異常對應的處理方法 Method method = this.exceptionLookupCache.get(exceptionType); // 其次,獲取不到,則從 mappedMethods 中得到,並添加到 exceptionLookupCache 中 if (method == null) { method = getMappedMethod(exceptionType); this.exceptionLookupCache.put(exceptionType, method); } return method; }
邏輯比較簡單,調用 getMappedMethod(Class<? extends Throwable> exceptionType)
方法,得到異常對應的方法,以下:
@Nullable private Method getMappedMethod(Class<? extends Throwable> exceptionType) { List<Class<? extends Throwable>> matches = new ArrayList<>(); // 遍歷 mappedMethods 數組,匹配異常,添加到 matches 中 for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) { if (mappedException.isAssignableFrom(exceptionType)) { matches.add(mappedException); } } // 將匹配的結果,排序,選擇第一個 if (!matches.isEmpty()) { matches.sort(new ExceptionDepthComparator(exceptionType)); return this.mappedMethods.get(matches.get(0)); } else { return null; } }
邏輯比較簡單,關於 org.springframework.core.ExceptionDepthComparator
比較器,胖友本身點擊 傳送門 查看。大致的邏輯是,比較它們和目標類的繼承層級,越小越匹配。
getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception)
方法,得到異常對應的 ServletInvocableHandlerMethod 對象,代碼以下:
@Nullable protected ServletInvocableHandlerMethod getExceptionHandlerMethod( @Nullable HandlerMethod handlerMethod, Exception exception) { // 處理器的類型 Class<?> handlerType = null; // <1> 首先,若是 handlerMethod 非空,則先得到 Controller 對應的 @ExceptionHandler 處理器對應的方法 if (handlerMethod != null) { // Local exception handler methods on the controller class itself. // To be invoked through the proxy, even in case of an interface-based proxy. // 得到 handlerType handlerType = handlerMethod.getBeanType(); // 得到 handlerType 對應的 ExceptionHandlerMethodResolver 對象 ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) { resolver = new ExceptionHandlerMethodResolver(handlerType); this.exceptionHandlerCache.put(handlerType, resolver); } // 得到異常對應的 Method 處理方法 Method method = resolver.resolveMethod(exception); // 若是得到該異常對應的 Method 處理方法,則建立 ServletInvocableHandlerMethod 對象,並返回 if (method != null) { return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); } // For advice applicability check below (involving base packages, assignable types // and annotation presence), use target class instead of interface-based proxy. // 得到 handlerType 的原始類。由於,此處有多是代理對象 if (Proxy.isProxyClass(handlerType)) { handlerType = AopUtils.getTargetClass(handlerMethod.getBean()); } } // <2> 其次,使用 ControllerAdvice 對應的 @ExceptionHandler 處理器對應的方法 for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { ControllerAdviceBean advice = entry.getKey(); // 若是 ControllerAdvice 支持當前的 handlerType if (advice.isApplicableToBeanType(handlerType)) { // 得到 handlerType 對應的 ExceptionHandlerMethodResolver 對象 ExceptionHandlerMethodResolver resolver = entry.getValue(); // 得到異常對應的 Method 處理方法 Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(advice.resolveBean(), method); } } } // 最差,獲取不到 return null; }
首先,若是 handlerMethod
非空,則先得到 Controller 對應的 @ExceptionHandler
處理器對應的方法,若是獲取到了,則將該 Method 封裝成 ServletInvocableHandlerMethod 對象並返回
其次,使用 ControllerAdvice
對應的 @ExceptionHandler
處理器對應的方法,若是獲取到了,則將該 Method 封裝成 ServletInvocableHandlerMethod 對象並返回
最差,獲取不到,返回 null
上面第 2
種狀況也就是示例中定義的方法哦~
實現 doResolveHandlerMethodException(ttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception)
方法,處理異常,代碼以下:
@Override @Nullable protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { // <1> 得到異常對應的 ServletInvocableHandlerMethod 對象 ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } // <1.1> 設置 ServletInvocableHandlerMethod 對象的相關屬性 if (this.argumentResolvers != null) { exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } // <1.2> 建立 ServletWebRequest 對象 ServletWebRequest webRequest = new ServletWebRequest(request, response); // <1.3> 建立 ModelAndViewContainer 對象 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); try { if (logger.isDebugEnabled()) { logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod); } // <2> // 執行處理該異常的方法 ServletInvocableHandlerMethod 的調用 Throwable cause = exception.getCause(); if (cause != null) { // Expose cause as provided argument as well exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); } else { // Otherwise, just the given exception as-is exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); } } catch (Throwable invocationEx) { // Any other than the original exception is unintended here, // probably an accident (e.g. failed assertion or the like). // <2.1> 發生異常,則直接返回 if (invocationEx != exception && logger.isWarnEnabled()) { logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx); } // Continue with default processing of the original exception... return null; } // <3> 若是 mavContainer 已處理,則返回 '空的' ModelAndView 對象。 if (mavContainer.isRequestHandled()) { return new ModelAndView(); } // <4> 若是 mavContainer 未處,則基於 `mavContainer` 生成 ModelAndView 對象 else { ModelMap model = mavContainer.getModel(); HttpStatus status = mavContainer.getStatus(); // <4.1> 建立 ModelAndView 對象,並設置相關屬性 ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); mav.setViewName(mavContainer.getViewName()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } // <4.2> if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } }
調用 getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception)
方法,得到異常對應的 ServletInvocableHandlerMethod 對象
webRequest
,封裝了請求和響應mavContainer
,用於獲取 ModelAndView 對象執行處理該異常的方法,ServletInvocableHandlerMethod 對象的調用
Object... providedArgs
參數爲 exception
和 handlerMethod
變量,這也是爲何 @ExceptionHanlder
註解的方法,能夠設置爲這兩個參數😈若是 mavContainer
已處理,則返回 「空的」 ModelAndView 對象。😈 這樣,就不會被後續的 ViewResolver 所處理。爲何呢?能夠本身回看下 DispatcherServlet 的 processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,很容易明白
若是 mavContainer
未處理,則基於 mavContainer
生成 ModelAndView 對象
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
,實現 MessageSourceAware 接口,繼承 AbstractHandlerExceptionResolver 抽象類,基於 @ResponseStatus
提供錯誤響應的 HandlerExceptionResolver 實現類
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware { @Nullable private MessageSource messageSource; }
applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
方法,設置錯誤響應,方法以下:
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response) throws IOException { // 狀況一,若是無錯誤提示,則響應只設置狀態碼 if (!StringUtils.hasLength(reason)) { response.sendError(statusCode); } // 狀況二,若是有錯誤信息,則響應設置狀態碼 + 錯誤提示 else { // 進一步解析錯誤提示,若是有 messageSource 的狀況下 String resolvedReason = (this.messageSource != null ? this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) : reason); // 設置 response.sendError(statusCode, resolvedReason); } // 建立「空」 ModelAndView 對象,並返回 return new ModelAndView(); }
注意,此處返回的也是「空」的 ModelAndView 對象。這樣,就不會被後續的 ViewResolver 所處理
實現 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,代碼以下:
@Override @Nullable protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { // <1> 狀況一,若是異常是 ResponseStatusException 類型,進行解析並設置到響應 if (ex instanceof ResponseStatusException) { return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler); } // <2> 狀況二,若是有 @ResponseStatus 註解,進行解析並設置到響應 ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (status != null) { return resolveResponseStatus(status, request, response, handler, ex); } // <3> 狀況三,使用異常的 cause 在走一次狀況1、狀況二的邏輯。 if (ex.getCause() instanceof Exception) { return doResolveException(request, response, handler, (Exception) ex.getCause()); } } catch (Exception resolveEx) { if (logger.isWarnEnabled()) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx); } } return null; }
狀況一,若是異常是 ResponseStatusException 類型,進行解析並設置到響應,調用 resolveResponseStatus(ResponseStatusException ex, HttpServletRequest request, HttpServletResponse response, Object handler)
方法,以下:
protected ModelAndView resolveResponseStatusException(ResponseStatusException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws Exception { int statusCode = ex.getStatus().value(); String reason = ex.getReason(); return applyStatusAndReason(statusCode, reason, response); }
狀況二,若是有 @ResponseStatus
註解,進行解析並設置到響應,調用 resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,以下:
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { int statusCode = responseStatus.code().value(); String reason = responseStatus.reason(); return applyStatusAndReason(statusCode, reason, response); }
狀況三,使用異常的 cause
再走一次狀況一、狀況二的邏輯
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
,繼承 AbstractHandlerExceptionResolver 抽象類,默認 HandlerExceptionResolver 實現類,針對各類異常,設置錯誤響應碼
其中,實現 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,代碼以下:
@Override @Nullable protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { 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); } else if (ex instanceof MissingPathVariableException) { return handleMissingPathVariable( (MissingPathVariableException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestParameterException) { return handleMissingServletRequestParameter( (MissingServletRequestParameterException) ex, request, response, handler); } else if (ex instanceof ServletRequestBindingException) { return handleServletRequestBindingException( (ServletRequestBindingException) ex, request, response, handler); } else if (ex instanceof ConversionNotSupportedException) { return handleConversionNotSupported( (ConversionNotSupportedException) ex, request, response, handler); } else if (ex instanceof TypeMismatchException) { return handleTypeMismatch( (TypeMismatchException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotReadableException) { return handleHttpMessageNotReadable( (HttpMessageNotReadableException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotWritableException) { return handleHttpMessageNotWritable( (HttpMessageNotWritableException) ex, request, response, handler); } else if (ex instanceof MethodArgumentNotValidException) { return handleMethodArgumentNotValidException( (MethodArgumentNotValidException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestPartException) { return handleMissingServletRequestPartException( (MissingServletRequestPartException) ex, request, response, handler); } else if (ex instanceof BindException) { return handleBindException((BindException) ex, request, response, handler); } else if (ex instanceof NoHandlerFoundException) { return handleNoHandlerFoundException( (NoHandlerFoundException) ex, request, response, handler); } else if (ex instanceof AsyncRequestTimeoutException) { return handleAsyncRequestTimeoutException( (AsyncRequestTimeoutException) ex, request, response, handler); } } catch (Exception handlerEx) { if (logger.isWarnEnabled()) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx); } } return null; }
邏輯不復雜,根據不一樣的異常,設置響應碼和錯誤信息,例如 HTTP 方法類型不支持,以下:
protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException { String[] supportedMethods = ex.getSupportedMethods(); if (supportedMethods != null) { response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", ")); } // 405 狀態碼,HTTP Method 不支持 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage()); return new ModelAndView(); }
注意,返回的都是「空」的 ModelAndView 對象。這樣,就不會被後續的 ViewResolver 所處理
本文對 Spring MVC 中的 HandlerExceptionResolver
組件進行分析,處理器異常解析器,將處理器( handler
)執行時發生的異常(也就是處理請求,執行方法的過程當中發生的異常)解析(轉換)成對應的 ModelAndView 結果
HandlerExceptionResolver
的實現類沒有特別多,不過也採用了組合模式,若是某個異常處理器進行處理了,也就是返回的 ModeAndView 不爲 null
(通常都是「空」對象),則直接返回該 ModeAndView 對象
在 Spring MVC 和 Spring Boot 中,默認狀況下都有三種 HandlerExceptionResolver 實現類,他們的順序以下:
ExceptionHandlerExceptionResolver
:基於 @ExceptionHandler
配置 HandlerMethod 的 HandlerExceptionResolver 實現類。例如經過 @ControllerAdvice
註解自定義異常處理器,加上@ExceptionHandler
註解指定方法所須要處理的異常類型,這種方式就在這個實現類中實現的。沒有使用過這兩個註解能夠參考上面的示例ResponseStatusExceptionResolver
:基於 @ResponseStatus
提供錯誤響應的 HandlerExceptionResolver 實現類。例如在方法上面添加 @ResponseStatus
註解,指定該方法發生異常時,須要設置的 code
響應碼和 reason
錯誤信息DefaultHandlerExceptionResolver
:默認 HandlerExceptionResolver 實現類,針對各類異常,設置錯誤響應碼。例如 HTTP Method 不支持,則在這個實現類中往響應中設置錯誤碼和錯誤信息到這裏,已經分析了 Spring MVC 的 DispatcherServlet,以及 MultipartResolver、HandlerMapping、HandlerAdapter 和 HandlerExceptionResolver 四個組件,只想說:Spring MVC 的設計者太膩害了~😛
參考文章:芋道源碼《精盡 Spring MVC 源碼分析》