1. 程式人生 > >精盡Spring MVC原始碼分析 - HandlerExceptionResolver 元件

精盡Spring MVC原始碼分析 - HandlerExceptionResolver 元件

> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring 版本:5.2.4.RELEASE > > 該系列其他文件請檢視:[**《精盡 Spring MVC 原始碼分析 - 文章導讀》**](https://www.cnblogs.com/lifullmoon/p/14123963.html) ## HandlerExceptionResolver 元件 `HandlerExceptionResolver` 元件,處理器異常解析器,將處理器( `handler` )執行時發生的異常(也就是處理請求,執行方法的過程中)解析(轉換)成對應的 ModelAndView 結果 ### 回顧 先來回顧一下在 `DispatcherServlet` 中處理請求的過程中哪裡使用到 `HandlerExceptionResolver` 元件,可以回到[**《一個請求的旅行過程》**](https://www.cnblogs.com/lifullmoon/p/14131862.html)中的 `DispatcherServlet` 的 `processHandlerException` 方法中看看,如下: ```java @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... // 遍歷 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; } } } // 情況一,生成了 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; } // 情況二,未生成 ModelAndView 物件,則丟擲異常 throw ex; } ``` 在 Spring MVC 的 `DispatcherServlet` 處理請求執行方法過程中,不管是否丟擲異常都會進行結果處理,如果丟擲了異常也需要呼叫該方法處理異常 可以看到,在 `` 處會遍歷所有的 `HandlerExceptionResolver` 異常處理器來處理,如果某一個處理器處理成功並返回 ModelAndView 物件,則直接返回 ### HandlerExceptionResolver 介面 `org.springframework.web.servlet.HandlerExceptionResolver`,異常處理器介面,程式碼如下: ```java public interface HandlerExceptionResolver { /** * 解析異常,轉換成對應的 ModelAndView 結果 */ @Nullable ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); } ``` HandlerExceptionResolver 介面體系的結構如下:
### 初始化過程 在 `DispatcherServlet` 的 `initHandlerExceptionResolvers(ApplicationContext context)` 方法,初始化 HandlerExceptionResolver 元件,方法如下: ```java private void initHandlerExceptionResolvers(ApplicationContext context) { // 置空 handlerExceptionResolvers 處理 this.handlerExceptionResolvers = null; // 情況一,自動掃描 HandlerExceptionResolver 型別的 Bean 們 if (this.detectAllHandlerExceptionResolvers) { // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. Map 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"); } } } ``` 1. 如果“開啟”探測功能,則掃描已註冊的 HandlerExceptionResolver 的 Bean 們,新增到 `handlerExceptionResolvers` 中,預設**開啟** 2. 如果“關閉”探測功能,則獲得 Bean 名稱為 "handlerExceptionResolver" 對應的 Bean ,將其新增至 `handlerExceptionResolvers` 3. 如果未獲得到,則獲得預設配置的 HandlerExceptionResolver 類,呼叫 `getDefaultStrategies(ApplicationContext context, Class strategyInterface)` 方法,就是從 `DispatcherServlet.properties` 檔案中讀取 HandlerExceptionResolver 的預設實現類,如下: ```properties 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` 中的這三種異常處理器進行分析 ### HandlerExceptionResolverComposite `org.springframework.web.servlet.handler.HandlerExceptionResolverComposite`,實現 HandlerExceptionResolver、Ordered 介面,複合的 HandlerExceptionResolver 實現類 #### 構造方法 ```java public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered { /** * 異常解析器陣列 */ @Nullable private List resolvers; /** * 優先順序,預設最低 */ private int order = Ordered.LOWEST_PRECEDENCE; } ``` - `resolvers`:HandlerExceptionResolver 實現類列表 - `order`:優先順序,預設最低 從上面的初始化過程中可以看到,Spring Boot 預設配置下 HandlerExceptionResolverComposite 包含三個實現類: 1. `org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver` 2. `org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver` 3. `org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver` #### resolveException 實現 `resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)` 方法,遍歷 HandlerExceptionResolver 陣列,逐個處理異常 `ex`,如果成功,則返回 ModelAndView 物件,方法如下: ```java @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; } ``` ### AbstractHandlerExceptionResolver `org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver`,實現 HandlerExceptionResolver、Ordered 介面,HandlerExceptionResolver 抽象類,作為所有 HandlerExceptionResolver 實現類的**基類** #### 構造方法 ```java 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 `shouldApplyTo(HttpServletRequest request, Object handler)` 方法,判斷當前 HandlerExceptionResolver 是否能應用到傳入的 `handler` 處理器,方法如下: ```java 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); } ``` 1. 如果 `mappedHandlers` 包含該 `handler` 處理器物件,則返回 `true` 2. 如果 `mappedHandlerClasses` 包含該 `handler` 處理器所在類,則返回 `true` 3. 如果 `mappedHandlers` 和 `mappedHandlerClasses` 都為空,說明直接匹配 #### prepareResponse `prepareResponse(Exception ex, HttpServletResponse response)` 方法,阻止響應快取,方法如下: ```java 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 實現 `resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)` 方法,程式碼如下: ```java @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); ``` 1. 呼叫 `shouldApplyTo(HttpServletRequest request, Object handler)` 方法,判斷是否可以應用,如果可以應用 1. 呼叫 `prepareResponse(Exception ex, HttpServletResponse response)` 方法,阻止快取 2. 呼叫 `doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)` 抽象方法,執行解析異常,返回 ModelAndView 物件 3. 如果 ModelAndView 物件非空,則列印日誌 4. 返回執行結果 2. 不可應用,直接返回 `null` ### AbstractHandlerMethodExceptionResolver `org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver`,繼承 AbstractHandlerExceptionResolver 抽象類,基於 `handler` 型別為 **HandlerMethod** 的 HandlerExceptionResolver 抽象類。 可能你會有疑惑,為什麼 AbstractHandlerMethodExceptionResolver 只有一個 ExceptionHandlerExceptionResolver 子類,為什麼還要做抽象呢?因為 ExceptionHandlerExceptionResolver 是基於 `@ExceptionHandler` 註解來配置對應的異常處理器,而如果未來我們想自定義其它的方式來配置對應的異常處理器,就可以來繼承 AbstractHandlerMethodExceptionResolver 這個抽象類。