精盡Spring MVC原始碼分析 - HandlerMapping 元件(四)之 AbstractUrlHandlerMapping
阿新 • • 發佈:2020-12-17
> 該系列文件是本人在學習 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 攔截器》**](https://www.cnblogs.com/lifullmoon/p/14137358.html)
- [**《HandlerMapping 元件(三)之 AbstractHandlerMethodMapping》**](https://www.cnblogs.com/lifullmoon/p/14137380.html)
- [**《HandlerMapping 元件(四)之 AbstractUrlHandlerMapping》**](https://www.cnblogs.com/lifullmoon/p/14137390.html)
## HandlerMapping 元件(四)之 AbstractUrlHandlerMapping
先來回顧一下HandlerMapping 介面體系的結構:
在[**《HandlerMapping 元件(一)之 AbstractHandlerMapping》**](https://www.cnblogs.com/lifullmoon/p/14137308.html)文件中已經分析了 HandlerMapping 元件的 **AbstractHandlerMapping** 抽象類基類
在[**《HandlerMapping 元件(三)之 AbstractHandlerMethodMapping》**](https://www.cnblogs.com/lifullmoon/p/14137380.html)文件中也已經分析了圖中**紅色框**部分的 **AbstractHandlerMethodMapping** 系,基於 Method 進行匹配。例如,我們所熟知的 @RequestMapping 等註解的方式。
那麼本文就接著來分析圖中**黃色框**部分的 **AbstractUrlHandlerMapping** 系,基於 URL 進行匹配。例如 [《基於 XML 配置的 Spring MVC 簡單的 HelloWorld 例項應用》](https://www.cnblogs.com/liuhongfeng/p/4769076.html) ,當然,目前這種方式已經基本不用了,被 `@RequestMapping` 等註解的方式所取代。不過,Spring MVC 內建的一些路徑匹配,還是使用這種方式。
> 因為 AbstractUrlHandlerMapping 在實際開發基本不會涉及到,所以本文**選讀**,可以直接檢視**總結**部分
一共有五個子類,分成兩條線:
- AbstractUrlHandlerMapping <= SimpleUrlHandlerMapping <= WebSocketHandlerMapping
- AbstractUrlHandlerMapping <= AbstractDetectingUrlHandlerMapping <= BeanNameUrlHandlerMapping
其中,WebSocketHandlerMapping 是 `spring-websocket` 專案中的類,本文會無視它
所以,本文按照 AbstractUrlHandlerMapping、SimpleUrlHandlerMapping、AbstractDetectingUrlHandlerMapping、BeanNameUrlHandlerMapping 順序進行分析
### 回顧
先來回顧一下在 `DispatcherServlet` 中處理請求的過程中通過 HandlerMapping 元件,獲取到 HandlerExecutionChain 處理器執行鏈的方法,是通過AbstractHandlerMapping 的 getHandler 方法來獲取的,如下:
```java
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// <1> 獲得處理器(HandlerMethod 或者 HandlerExecutionChain),該方法是抽象方法,由子類實現
Object handler = getHandlerInternal(request);
// <2> 獲得不到,則使用預設處理器
// <3> 還是獲得不到,則返回 null
// <4> 如果找到的處理器是 String 型別,則從 Spring 容器中找到對應的 Bean 作為處理器
// <5> 建立 HandlerExecutionChain 物件(包含處理器和攔截器)
// ... 省略相關程式碼
return executionChain;
}
```
在 AbstractHandlerMapping 獲取 HandlerExecutionChain 處理器執行鏈的方法中,需要先呼叫 `getHandlerInternal(HttpServletRequest request)` 抽象方法,獲取請求對應的處理器,該方法由子類去實現,也就上圖中**黃色框**和**紅色框**兩類子類,本文分析**黃色框**部分內容
### AbstractUrlHandlerMapping
`org.springframework.web.servlet.handler.AbstractUrlHandlerMapping`,實現 MatchableHandlerMapping 介面,繼承 AbstractHandlerMapping 抽象類,以 **URL** 作為 **Handler 處理器** 的 HandlerMapping 抽象類,提供 Handler 的獲取、註冊等等通用的骨架方法。
#### 構造方法
```java
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
/**
* 根路徑("/")的處理器
*/
@Nullable
private Object rootHandler;
/**
* 使用後置的 / 匹配
*/
private boolean useTrailingSlashMatch = false;
/**
* 是否延遲載入處理器,預設關閉
*/
private boolean lazyInitHandlers = false;
/**
* 路徑和處理器的對映
*
* KEY:路徑 {@link #lookupHandler(String, HttpServletRequest)}
*/
private final Map handlerMap = new LinkedHashMap<>();
}
```
#### registerHandler
`registerHandler(String[] urlPaths, String beanName)` 方法,註冊多個 URL 的處理器,方法如下:
```java
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
// <1> 如果非延遲載入,並且 handler 為 String 型別,並且還是單例,則去獲取 String 對應的 Bean 物件
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
resolvedHandler = applicationContext.getBean(handlerName);
}
}
// <2> 獲得 urlPath 對應的處理器
Object mappedHandler = this.handlerMap.get(urlPath);
// <3> 檢驗 mappedHandler 是否已存在,如果已存在,並且不是當前 resolvedHandler 物件,則丟擲異常
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
// <4.1> 如果是 / 根路徑,則設定為 rootHandler
if (urlPath.equals("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
// <4.2> 如果是 /* 路徑,則設定為預設處理器
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
// <4.3> 新增到 handlerMap 中
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
```
遍歷 URL,依次註冊處理器
1. 如果非延遲載入,並且 `handler` 為 String 型別,並且還是單例,則去獲取 String 對應的 Bean 物件,`resolvedHandler `
2. 從`handlerMap`中獲得 `urlPath` 對應的處理器
3. 如果該路徑已存在對應的處理器,但是不是當前 `resolvedHandler` 物件,則丟擲異常
4. 否則,該路徑不存在對應的處理器,則將當前 `resolvedHandler` 處理器儲存
1. 如果是 `/` 根路徑,則設定 `resolvedHandler` 為 `rootHandler`
2. 否則,如果是 `/*` 路徑,則設定為預設處理器 `defaultHandler`(在父類中)
3. 否則,新增到 `handlerMap` 中
#### getHandlerInternal
實現父類的 `getHandlerInternal(HttpServletRequest request)` 方法,獲得處理器,方法如下:
```java
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// <1> 獲得請求的路徑
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// <2> 獲得處理器
Object handler = lookupHandler(lookupPath, request);
// <3> 如果找不到處理器,則使用 rootHandler 或 defaultHandler 處理器
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
// <3.1> 如果是根路徑,則使用 rootHandler 處理器
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
// <3.2> 使用預設處理器
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
// <3.3> 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
// <3.4> 空方法,校驗處理器。目前暫無子類實現該方法
validateHandler(rawHandler, request);
// <3.5> 建立處理器(HandlerExecutionChain 物件)
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
```
1. 獲得請求路徑
2. 呼叫 `lookupHandler(String urlPath, HttpServletRequest request)` 方法,獲得處理器,詳情見下文
3. 如果找不到處理器,則使用 `rootHandler` 或 `defaultHandler` 處理器
1. 如果是`/`根路徑,則使用 `rootHandler` 處理器
2. 否則,使用 `defaultHandler` 預設處理器
3. 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
4. 呼叫`validateHandler(Object handler, HttpServletRequest request)`,對處理器進行校驗,空方法,暫無子類實現該方法
5. 呼叫 `buildPathExposingHandler`方法,建立 HandlerExecutionChain 處理器執行鏈,賦值給`handler`處理器,詳情見下文
4. 返回請求對應的`handler`處理器
所以說這裡但會的處理器物件可能是一個 HandlerExecutionChain 物件,用途目前不清楚