1. 程式人生 > >精盡Spring MVC原始碼分析 - HandlerMapping 元件(一)之 AbstractHandlerMapping

精盡Spring MVC原始碼分析 - HandlerMapping 元件(一)之 AbstractHandlerMapping

> 該系列文件是本人在學習 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) ## HandlerMapping 元件 HandlerMapping 元件,請求的**處理器匹配器**,負責為請求找到合適的 `HandlerExecutionChain` 處理器執行鏈,包含處理器(`handler`)和攔截器們(`interceptors`) - `handler` 處理器是 Object 型別,可以將其理解成 HandlerMethod 物件(例如我們使用最多的 `@RequestMapping` 註解所標註的方法會解析成該物件),包含了方法的所有資訊,通過該物件能夠執行該方法 - `HandlerInterceptor` 攔截器對處理請求進行**增強處理**,可用於在執行方法前、成功執行方法後、處理完成後進行一些邏輯處理 由於 HandlerMapping 元件涉及到的內容比較多,考慮到內容的排版,所以將這部分內容拆分成了四個模組,依次進行分析: - [**《HandlerMapping 元件(一)之 AbstractHandlerMapping》**](https://www.cnblogs.com/lifullmoon/p/14137308.html) - **《HandlerMapping 元件(二)之 HandlerInterceptor 攔截器》** - **《HandlerMapping 元件(三)之 AbstractHandlerMethodMapping》** - **《HandlerMapping 元件(四)之 AbstractUrlHandlerMapping》** ## HandlerMapping 元件(一)之 AbstractHandlerMapping 先來回顧一下在 `DispatcherServlet` 中處理請求的過程中哪裡使用到 `HandlerMapping` 元件,可以回到[**《一個請求的旅行過程》**](https://www.cnblogs.com/lifullmoon/p/14131862.html)中的 `DispatcherServlet` 的 `doDispatch` 方法中看看,如下: ```java protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; // ... 省略相關程式碼 // Determine handler for the current request. // <3> 獲得請求對應的 HandlerExecutionChain 物件(HandlerMethod 和 HandlerInterceptor 攔截器們) mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // <3.1> 如果獲取不到,則根據配置丟擲異常或返回 404 錯誤 noHandlerFound(processedRequest, response); return; } // ... 省略相關程式碼 } @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { // 遍歷 handlerMappings 元件們 for (HandlerMapping mapping : this.handlerMappings) { // 通過 HandlerMapping 元件獲取到 HandlerExecutionChain 物件 HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { // 不為空則直接返回 return handler; } } } return null; } ``` 通過遍歷 HandlerMapping 元件們,根據請求獲取到對應 HandlerExecutionChain 處理器執行鏈。**注意**,這裡是通過一個一個的 HandlerMapping 元件去進行處理,如果找到對應 HandlerExecutionChain 物件則直接返回,不會繼續下去,所以初始化的 HandlerMapping 元件是有一定的先後順序的,預設是BeanNameUrlHandlerMapping -> RequestMappingHandlerMapping ### HandlerMapping 介面 `org.springframework.web.servlet.HandlerMapping` 介面,請求的處理器匹配器,負責為請求找到合適的 `HandlerExecutionChain` 處理器執行鏈,包含處理器(`handler`)和攔截器們(`interceptors`),程式碼如下: ```java public interface HandlerMapping { String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler"; String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping"; String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern"; String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping"; String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables"; String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables"; String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes"; /** * 獲得請求對應的處理器和攔截器們 */ @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; } ``` ### 類圖 HandlerMapping 介面體系的結構如下:
- 藍色框 **AbstractHandlerMapping** 抽象類,實現了“為請求找到合適的 `HandlerExecutionChain` 處理器執行鏈”對應的的骨架邏輯,而暴露 `getHandlerInternal(HttpServletRequest request)` 抽象方法,交由子類實現。 - AbstractHandlerMapping 的子類,分成兩派,分別是: - 黃色框 **AbstractUrlHandlerMapping** 系,基於 URL 進行匹配。例如 [《基於 XML 配置的 Spring MVC 簡單的 HelloWorld 例項應用》](https://www.cnblogs.com/liuhongfeng/p/4769076.html) ,當然,目前這種方式已經基本不用了,被 `@RequestMapping` 等註解的方式所取代。不過,Spring MVC 內建的一些路徑匹配,還是使用這種方式。 - 紅色框 **AbstractHandlerMethodMapping** 系,基於 Method 進行匹配。例如,我們所熟知的 `@RequestMapping` 等註解的方式。 - 綠色框的 MatchableHandlerMapping 介面,定義了“判斷請求和指定 `pattern` 路徑是否匹配”的方法。 ### 初始化過程 在 `DispatcherServlet` 的 `initHandlerMappings(ApplicationContext context)` 方法,會在 `onRefresh` 方法被呼叫,初始化 HandlerMapping 元件,方法如下: ```java private void initHandlerMappings(ApplicationContext context) { // 置空 handlerMappings this.handlerMappings = null; // <1> 如果開啟探測功能,則掃描已註冊的 HandlerMapping 的 Bean 們,新增到 handlerMappings 中 if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. // 掃描已註冊的 HandlerMapping 的 Bean 們 Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); // 新增到 handlerMappings 中,並進行排序 if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } } // <2> 如果關閉探測功能,則獲得 Bean 名稱為 "handlerMapping" 對應的 Bean ,將其新增至 handlerMappings else { try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerMapping later. } } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. /** * <3> 如果未獲得到,則獲得預設配置的 HandlerMapping 類 * {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping} */ if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } } ``` 1. 如果“開啟”探測功能,則掃描已註冊的 HandlerMapping 的 Bean 們,新增到 `handlerMappings` 中,預設**開啟** 2. 如果“關閉”探測功能,則獲得 Bean 名稱為 "handlerMapping" 對應的 Bean ,將其新增至 `handlerMappings` 3. 如果未獲得到,則獲得預設配置的 HandlerMapping 類,呼叫 `getDefaultStrategies(ApplicationContext context, Class strategyInterface)` 方法,就是從 `DispatcherServlet.properties` 檔案中讀取 HandlerMapping 的預設實現類,如下: ```properties org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping ``` 可以看到對應的是 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping 物件 ### AbstractHandlerMapping `org.springframework.web.servlet.handler.AbstractHandlerMapping`,實現 **HandlerMapping**、Ordered、BeanNameAware 介面,繼承 WebApplicationObjectSupport 抽象類 該類是 HandlerMapping 介面的**抽象基類**,實現了“為請求找到合適的 `HandlerExecutionChain` 處理器執行鏈”對應的的骨架邏輯,而暴露 `getHandlerInternal(HttpServletRequest request)` 抽象方法,交由子類實現 WebApplicationObjectSupport 抽象類,提供 `applicationContext` 屬性的宣告和注入。 #### 構造方法 ```java public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware { /** * 預設處理器 */ @Nullable private Object defaultHandler; /** * URL 路徑工具類 */ private UrlPathHelper urlPathHelper = new UrlPathHelper(); /** * 路徑匹配器 */ private PathMatcher pathMatcher = new AntPathMatcher(); /** * 配置的攔截器陣列. * * 在 {@link #initInterceptors()} 方法中,初始化到 {@link #adaptedInterceptors} 中 * * 新增方式有兩種: * 1. {@link #setInterceptors(Object...)} 方法 * 2. {@link #extendInterceptors(List)} 方法 */ private final List interceptors = new ArrayList<>(); /** * 初始化後的攔截器 HandlerInterceptor 陣列 */ private final List adaptedInterceptors = new ArrayList<>(); private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource(); private CorsProcessor corsProcessor = new DefaultCorsProcessor(); private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered /** * 當前 Bean 的名稱 */ @Nullable private String beanName; // ... 省略相關 getter、setter 方法 } ``` - `defaultHandler`:**預設**處理器,在獲得不到處理器時,可使用該屬性 - `interceptors`:**配置**的攔截器陣列 - `adaptedInterceptors`:**初始化後**的攔截器 HandlerInterceptor 陣列,也就是`interceptors` 轉換成的 HandlerInterceptor 攔截器物件 #### initApplicationContext `initApplicationContext()`方法,用於初始化攔截器們,方法如下: > 在父類 WebApplicationObjectSupport 的父類 ApplicationObjectSupport 中可以看到,因為實現了 ApplicationContextAware 介面,則在初始化該 Bean 的時候會呼叫 `setApplicationContext(@Nullable ApplicationContext context)` 方法,在這個方法中會呼叫 `initApplicationContext()` 這個方法 ```java @Override protected void initApplicationContext() throws BeansException { // <1> 空實現,交給子類實現,用於註冊自定義的攔截器到 interceptors 中,目前暫無子類實現 extendInterceptors(this.interceptors); // <2> 掃描已註冊的 MappedInterceptor 的 Bean 們,新增到 mappedInterceptors 中 detectMappedInterceptors(this.adaptedInterceptors); // <3> 將 interceptors 初始化成 HandlerInterceptor 型別,新增到 mappedInterceptors 中 initInterceptors(); } ``` 1. 呼叫 `extendInterceptors(List interceptors)` 方法,空方法,目前暫無子類實現,暫時忽略 2. 呼叫 `detectMappedInterceptors(List mappedInterceptors)` 方法,從 Spring 的上下文中,掃描已註冊的 MappedInterceptor 的攔截器們,新增到 `adaptedInterceptors` 中,方法如下: ```java protected void detectMappedInterceptors(List mappedInterceptors) { // 掃描已註冊的 MappedInterceptor 的 Bean 們,新增到 mappedInterceptors 中 // MappedInterceptor 會根據請求路徑做匹配,是否進行攔截 mappedInterceptors.addAll(BeanFactoryUtils .beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false) .values()); } ``` 3. 呼叫 `initInterceptors()` 方法,將 `interceptors` 初始化成 HandlerInterceptor 型別,新增到 `adaptedInterceptors` 中,方法如下: ```java protected void initInterceptors() { if (!this.interceptors.isEmpty()) { for (int i = 0; i < this.interceptors.size(); i++) { Object interceptor = this.interceptors.get(i); if (interceptor == null) { throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null"); } // 將 interceptors 初始化成 HandlerInterceptor 型別,新增到 mappedInterceptors 中 // 注意,HandlerInterceptor 無需進行路徑匹配,直接攔截全部 this.adaptedInterceptors.add(adaptInterceptor(interceptor)); } } } protected HandlerInterceptor adaptInterceptor(Object interceptor) { if (interceptor instanceof HandlerInterceptor) { return (HandlerInterceptor) interceptor; } else if (interceptor instanceof WebRequestInterceptor) { return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor); } else { throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName()); } } ``` 關於攔截器在後文進行分析 #### getHandler `getHandler(HttpServletRequest request)` 方法,獲得請求對應的 `HandlerExecutionChain` 處理器執行鏈,包含處理器(`handler`)和攔截器們(`interceptors`),方法如下: ```java @Override @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // <1> 獲得處理器(HandlerMethod 或者 HandlerExecutionChain),該方法是抽象方法,由子類實現 Object handler = getHandlerInternal(request); // <2> 獲得不到,則使用預設處理器 if (handler == null) { handler = getDefaultHandler(); } // <3> 還是獲得不到,則返回 null if (handler == null) { return null; } // Bean name or resolved handler? // <4> 如果找到的處理器是 String 型別,則從 Spring 容器中找到對應的 Bean 作為處理器 if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // <5> 建立 HandlerExecutionChain 物件(包含處理器和攔截器) HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { logger.debug("Mapped to " + executionChain.getHandler()); } if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; } ``` 1. 呼叫`getHandlerInternal(HttpServletRequest request)` **抽象**方法,獲得 `handler` 處理器 2. 如果 `handler` 處理器沒有找到,則呼叫`getDefaultHandler()` 方法,使用預設處理器,也就是 `defaultHandler` 屬性 3. 如果 `handler` 處理器沒有找到,且沒有預設的處理器,則直接返回 `null` 4. 如果找到的處理器是 String 型別,可能是 Bean 的名稱,則從 Spring 容器中找到對應的 Bean 作為處理器 5. 呼叫 `getHandlerExecutionChain(Object handler, HttpServletRequest request)` 方法,獲得 HandlerExecutionChain 物件,方法如下: ```java protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { // <1> 建立 HandlerExecutionChain 物件 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); // <2> 獲得請求路徑 String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); // <3> 遍歷 adaptedInterceptors 陣列,獲得請求匹配的攔截器 for (HandlerInterceptor interceptor : this.adaptedInterceptors) { // 需要匹配,若路徑匹配,則新增到 chain 中 if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { // 匹配 chain.addInterceptor(mappedInterceptor.getInterceptor()); } } // 無需匹配,直接新增到 chain 中 else { chain.addInterceptor(interceptor); } } return chain } ``` 1. 建立一個 HandlerExecutionChain 物件,如果 `handler` 處理器就是該型別物件,則直接使用 2. 獲得請求路徑 3. 遍歷 `adaptedInterceptors` 攔截器陣列,根據請求路徑獲得當前請求匹配的攔截器們,新增到 HandlerExecutionChain 物件中 6. 返回上面建立的 HandlerExecutionChain 物件 ### MatchableHandlerMapping `org.springframework.web.servlet.handler.MatchableHandlerMapping`,定義了“判斷請求和指定 `pattern` 路徑是否匹配”的方法。程式碼如下: ```java public interface MatchableHandlerMapping extends HandlerMapping { /** * 判斷請求和指定 pattern 路徑是否匹配 */ @Nullable RequestMatchResult match(HttpServletRequest request, String pattern); } ``` #### RequestMatchResult `org.springframework.web.servlet.handler.RequestMatchResult` 類,判斷請求和指定 `pattern` 路徑是否匹配時,返回的匹配結果,程式碼如下: ```java public class RequestMatchResult { /** * 匹配到的路徑 */ private final String matchingPattern; /** * 被匹配的路徑 */ private final String lookupPath; /** * 路徑匹配器 */ private final PathMatcher pathMatcher; public RequestMatchResult(String matchingPattern, String lookupPath, PathMatcher pathMatcher) { Assert.hasText(matchingPattern, "'matchingPattern' is required"); Assert.hasText(lookupPath, "'lookupPath' is required"); Assert.notNull(pathMatcher, "'pathMatcher' is required"); this.matchingPattern = matchingPattern; this.lookupPath = lookupPath; this.pathMatcher = pathMatcher; } public Map extractUriTemplateVariables() { return this.pathMatcher.extractUriTemplateVariables(this.matchingPattern, this.lookupPath); } } ``` 目前實現 MatchableHandlerMapping 介面的類,有 RequestMappingHandlerMapping 類和 AbstractUrlHandlerMapping 抽象類,在後續都會進行分析 ### 總結 本文對 Spring MVC 處理請求的過程中使用到的 HandlerMapping 元件進行了分析,會為請求找到合適的 `HandlerExecutionChain` 處理器執行鏈,包含處理器(`handler`)和攔截器們(`interceptors`) HandlerMapping 元件的實現類分為兩種: - 基於 URL 進行匹配。例如 [《基於 XML 配置的 Spring MVC 簡單的 HelloWorld 例項應用》](https://www.cnblogs.com/liuhongfeng/p/4769076.html) ,當然,目前這種方式已經基本不用了,被 `@RequestMapping` 等註解的方式所取代。不過,Spring MVC 內建的一些路徑匹配,還是使用這種方式 - 基於 Method 進行匹配。例如,我們所熟知的 `@RequestMapping` 等註解的方式 AbstractHandlerMapping 抽象類,作為一個基類,實現了“為請求找到合適的 `HandlerExecutionChain` 處理器執行鏈”對應的的骨架邏輯,而暴露 `getHandlerInternal(HttpServletRequest request)` 抽象方法,交由子類實現。 本文對 HandlerMapping 元件做了一個簡單的介紹,更多的細節交由其子類去實現,由於涉及到的內容比較多,**BeanNameUrlHandlerMapping** 和 **RequestMappingHandlerMapping** 兩種實現類則在後續的文件中依次進行分析 > 參考文章:**芋道原始碼**[《精盡 Spring MVC 原始碼分析》](http://svip.iocoder.cn/categories/Spri