該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀
Spring 版本:5.2.4.RELEASE
該系列其他文件請檢視:《精盡 Spring MVC 原始碼分析 - 文章導讀》
HandlerMapping 元件
HandlerMapping 元件,請求的處理器匹配器,負責為請求找到合適的 HandlerExecutionChain
處理器執行鏈,包含處理器(handler
)和攔截器們(interceptors
)
-
handler
處理器是 Object 型別,可以將其理解成 HandlerMethod 物件(例如我們使用最多的@RequestMapping
註解所標註的方法會解析成該物件),包含了方法的所有資訊,通過該物件能夠執行該方法 -
HandlerInterceptor
攔截器對處理請求進行增強處理,可用於在執行方法前、成功執行方法後、處理完成後進行一些邏輯處理
由於 HandlerMapping 元件涉及到的內容比較多,考慮到內容的排版,所以將這部分內容拆分成了四個模組,依次進行分析:
- 《HandlerMapping 元件(一)之 AbstractHandlerMapping》
- 《HandlerMapping 元件(二)之 HandlerInterceptor 攔截器》
- 《HandlerMapping 元件(三)之 AbstractHandlerMethodMapping》
- 《HandlerMapping 元件(四)之 AbstractUrlHandlerMapping》
HandlerMapping 元件(四)之 AbstractUrlHandlerMapping
先來回顧一下HandlerMapping 介面體系的結構:
在《HandlerMapping 元件(一)之 AbstractHandlerMapping》文件中已經分析了 HandlerMapping 元件的 AbstractHandlerMapping 抽象類基類
在《HandlerMapping 元件(三)之 AbstractHandlerMethodMapping》文件中也已經分析了圖中紅色框部分的 AbstractHandlerMethodMapping 系,基於 Method 進行匹配。例如,我們所熟知的 @RequestMapping 等註解的方式。
那麼本文就接著來分析圖中黃色框部分的 AbstractUrlHandlerMapping 系,基於 URL 進行匹配。例如 《基於 XML 配置的 Spring MVC 簡單的 HelloWorld 例項應用》 ,當然,目前這種方式已經基本不用了,被 @RequestMapping
等註解的方式所取代。不過,Spring MVC 內建的一些路徑匹配,還是使用這種方式。
因為 AbstractUrlHandlerMapping 在實際開發基本不會涉及到,所以本文選讀,可以直接檢視總結部分
一共有五個子類,分成兩條線:
- AbstractUrlHandlerMapping <= SimpleUrlHandlerMapping <= WebSocketHandlerMapping
- AbstractUrlHandlerMapping <= AbstractDetectingUrlHandlerMapping <= BeanNameUrlHandlerMapping
其中,WebSocketHandlerMapping 是 spring-websocket
專案中的類,本文會無視它
所以,本文按照 AbstractUrlHandlerMapping、SimpleUrlHandlerMapping、AbstractDetectingUrlHandlerMapping、BeanNameUrlHandlerMapping 順序進行分析
回顧
先來回顧一下在 DispatcherServlet
中處理請求的過程中通過 HandlerMapping 元件,獲取到 HandlerExecutionChain 處理器執行鏈的方法,是通過AbstractHandlerMapping 的 getHandler 方法來獲取的,如下:
@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 的獲取、註冊等等通用的骨架方法。
構造方法
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<String, Object> handlerMap = new LinkedHashMap<>();
}
registerHandler
registerHandler(String[] urlPaths, String beanName)
方法,註冊多個 URL 的處理器,方法如下:
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,依次註冊處理器
- 如果非延遲載入,並且
handler
為 String 型別,並且還是單例,則去獲取 String 對應的 Bean 物件,resolvedHandler
- 從
handlerMap
中獲得urlPath
對應的處理器 - 如果該路徑已存在對應的處理器,但是不是當前
resolvedHandler
物件,則丟擲異常 - 否則,該路徑不存在對應的處理器,則將當前
resolvedHandler
處理器儲存- 如果是
/
根路徑,則設定resolvedHandler
為rootHandler
- 否則,如果是
/*
路徑,則設定為預設處理器defaultHandler
(在父類中) - 否則,新增到
handlerMap
中
- 如果是
getHandlerInternal
實現父類的 getHandlerInternal(HttpServletRequest request)
方法,獲得處理器,方法如下:
@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;
}
-
獲得請求路徑
-
呼叫
lookupHandler(String urlPath, HttpServletRequest request)
方法,獲得處理器,詳情見下文 -
如果找不到處理器,則使用
rootHandler
或defaultHandler
處理器- 如果是
/
根路徑,則使用rootHandler
處理器 - 否則,使用
defaultHandler
預設處理器 - 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
- 呼叫
validateHandler(Object handler, HttpServletRequest request)
,對處理器進行校驗,空方法,暫無子類實現該方法 - 呼叫
buildPathExposingHandler
方法,建立 HandlerExecutionChain 處理器執行鏈,賦值給handler
處理器,詳情見下文
- 如果是
-
返回請求對應的
handler
處理器
所以說這裡但會的處理器物件可能是一個 HandlerExecutionChain 物件,用途目前不清楚? ? 先繼續往下看
lookupHandler
lookupHandler(String urlPath, HttpServletRequest request)
方法,獲得請求對應的處理器,方法如下:
@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
// <1.1> 情況一,從 handlerMap 中,直接匹配處理器
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
// <1.2> 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// <1.3> 空方法,校驗處理器。目前暫無子類實現該方法
validateHandler(handler, request);
// <1.4> 建立處理器
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
List<String> matchingPatterns = new ArrayList<>();
// <2.1> 情況二,Pattern 匹配合適的,並新增到 matchingPatterns 中
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) { // 路徑通過Pattern匹配成功
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
// <2.2> 獲得首個匹配(最優)的結果
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
// 排序
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
logger.trace("Matching patterns " + matchingPatterns);
}
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) {
// <2.3> 獲得 bestMatch 對應的處理器
handler = this.handlerMap.get(bestMatch);
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
if (handler == null) { // 如果獲得不到,丟擲 IllegalStateException 異常
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// <2.4> 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// <2.5> 空方法,校驗處理器。目前暫無子類實現該方法
validateHandler(handler, request);
// <2.6> 獲得匹配的路徑
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
// <2.7> 獲得路徑引數集合
Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
logger.trace("URI variables " + uriTemplateVariables);
}
// <2.8> 建立處理器
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
-
情況一
- 從
handlerMap
中,直接匹配處理器 - 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
- 校驗處理器,空方法,暫無子類實現,暫時忽略
- 建立處理器,直接返回,這裡是 HandlerExecutionChain 型別,呼叫
buildPathExposingHandler
方法,詳情見下文
- 從
-
情況二
- Pattern 匹配合適的,並新增到
matchingPatterns
中 - 獲得首個匹配(最優)的結果
bestMatch
- 獲得
bestMatch
對應的處理器,如果獲得不到,丟擲異常 - 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
- 校驗處理器,空方法,暫無子類實現,暫時忽略
- 獲得請求最匹配的路徑
pathWithinMapping
- 獲得匹配的路徑引數集合
uriTemplateVariables
- 建立處理器,直接返回,這裡是 HandlerExecutionChain 型別,呼叫
buildPathExposingHandler
方法,詳情見下文
- Pattern 匹配合適的,並新增到
-
都不匹配則返回
null
buildPathExposingHandler
buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables)
方法
構建一個 HandlerExecutionChain 型別的處理器,新增兩個攔截器,方法如下:
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
// <1> 建立 HandlerExecutionChain 物件
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
// <2> 新增 PathExposingHandlerInterceptor 攔截器,到 chain 中
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
// <3> 新增 UriTemplateVariablesHandlerInterceptor 攔截器,到 chain 中
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
- 建立 HandlerExecutionChain 型別的處理器物件
- 新增 PathExposingHandlerInterceptor 攔截器,用於暴露
bestMatchingPattern
屬性到請求中 - 新增 UriTemplateVariablesHandlerInterceptor 攔截器,用於暴露
uriTemplateVariables
屬性到請求中
兩個攔截器如下:
private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {
/** 最佳匹配的路徑 */
private final String bestMatchingPattern;
/** 被匹配的路徑 */
private final String pathWithinMapping;
public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
this.bestMatchingPattern = bestMatchingPattern;
this.pathWithinMapping = pathWithinMapping;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handler);
request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
return true;
}
}
protected void exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping,
HttpServletRequest request) {
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
request.setAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}
private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter {
private final Map<String, String> uriTemplateVariables;
public UriTemplateVariablesHandlerInterceptor(Map<String, String> uriTemplateVariables) {
this.uriTemplateVariables = uriTemplateVariables;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposeUriTemplateVariables(this.uriTemplateVariables, request);
return true;
}
}
protected void exposeUriTemplateVariables(Map<String, String> uriTemplateVariables, HttpServletRequest request) {
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
}
都是往請求中設定相關屬性,用途目前不清楚? ? 先繼續往下看
match
match(HttpServletRequest request, String pattern)
方法,執行匹配,程式碼如下:
@Override
@Nullable
public RequestMatchResult match(HttpServletRequest request, String pattern) {
// <1> 獲得請求路徑
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// <2> 模式匹配,若匹配,則返回 RequestMatchResult 物件
if (getPathMatcher().match(pattern, lookupPath)) {
return new RequestMatchResult(pattern, lookupPath, getPathMatcher());
}
else if (useTrailingSlashMatch()) {
if (!pattern.endsWith("/") && getPathMatcher().match(pattern + "/", lookupPath)) {
return new RequestMatchResult(pattern + "/", lookupPath, getPathMatcher());
}
}
return null;
}
- 獲得請求路徑
- 模式匹配,若匹配,則返回 RequestMatchResult 物件
SimpleUrlHandlerMapping
org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
,繼承 AbstractUrlHandlerMapping 抽象類,簡單的就 URL 匹配的 HandlerMapping 實現類
使用示例
在接觸 Spring MVC 比較早,你也許見過這樣配置
<!-- 定義一個 helloController Bean,實現了 Controller 介面 -->
<bean id="helloController" class="com.fullmoon.study.controller.HelloController"/>
<!-- 定義請求處理對映 HandlerMapping -->
<bean class="org.springframework.web.servlet.handler. SimpleUrlHandlerMapping">
<property name="mappings" ref="urlMappings" />
</bean>
<!-- 定義請求對映表 map -->
<util:properties id="urlMappings">
<prop key="/hello.form">helloController</prop>
</util:properties>
當然,上述這種配置基本已經不存在了,因為被 @RequestMapping
註解這樣的方式所取代。更多的是 Spring MVC 自己內部的元件可能在使用這種型別的 HandlerMapping ,例如下圖:
構造方法
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
/**
* 配置的 URL 與處理器的對映
*
* 最終,會呼叫 {@link #registerHandlers(Map)} 進行註冊到 {@link AbstractUrlHandlerMapping#handlerMap} 中
*/
private final Map<String, Object> urlMap = new LinkedHashMap<>();
public void setMappings(Properties mappings) {
CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
}
public void setUrlMap(Map<String, ?> urlMap) {
this.urlMap.putAll(urlMap);
}
}
例如上面的配置示例就會通過 setMappings(Properties mappings)
方法,將 /hello.form
與 HelloController
設定到 urlMap
中
所以說處理器也可能是一個 Controller 介面
initApplicationContext
initApplicationContext()
方法,用於初始化,將 urlMap
中的URL與處理器新增到父類的 handlerMap
中
在父類 WebApplicationObjectSupport 的父類 ApplicationObjectSupport 中可以看到,因為實現了 ApplicationContextAware 介面,則在初始化該 Bean 的時候會呼叫
setApplicationContext(@Nullable ApplicationContext context)
方法,在這個方法中會呼叫initApplicationContext()
這個方法在父類 AbstractHandlerMapping 中,該方法會初始化攔截器們
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.trace("No patterns in " + formatMappingName());
}
else {
urlMap.forEach((url, handler) -> {
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
// 【核心程式碼】註冊處理器
registerHandler(url, handler);
});
if (logger.isDebugEnabled()) {
List<String> patterns = new ArrayList<>();
if (getRootHandler() != null) {
patterns.add("/");
}
if (getDefaultHandler() != null) {
patterns.add("/**");
}
patterns.addAll(getHandlerMap().keySet());
logger.debug("Patterns " + patterns + " in " + formatMappingName());
}
}
}
邏輯很簡單,呼叫父類的registerHandler(String urlPath, Object handler)
方法,新增註冊器
AbstractDetectingUrlHandlerMapping
org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping
,繼承 AbstractUrlHandlerMapping 抽象類,自動探測的基於 URL 匹配的 HandlerMapping 抽象實現類
構造方法
public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {
/**
* 是否只掃描可訪問的 Handler 們
*/
private boolean detectHandlersInAncestorContexts = false;
public void setDetectHandlersInAncestorContexts(boolean detectHandlersInAncestorContexts) {
this.detectHandlersInAncestorContexts = detectHandlersInAncestorContexts;
}
}
initApplicationContext
initApplicationContext()
方法,用於初始化,找到符合條件的處理器,新增到父類的 handlerMap
中
在父類 WebApplicationObjectSupport 的父類 ApplicationObjectSupport 中可以看到,因為實現了 ApplicationContextAware 介面,則在初始化該 Bean 的時候會呼叫
setApplicationContext(@Nullable ApplicationContext context)
方法,在這個方法中會呼叫initApplicationContext()
這個方法在父類 AbstractHandlerMapping 中,該方法會初始化攔截器們
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
// 自動探測處理器
detectHandlers();
}
protected void detectHandlers() throws BeansException {
// <1> 從 Spring 上下文獲取所有 Object 型別的 Bean 的名稱們
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
// <2> 遍歷所有的 Bean ,逐個註冊
for (String beanName : beanNames) {
// <2.1> 獲得 Bean 對應的 URL 們
String[] urls = determineUrlsForHandler(beanName);
// <2.2> 如果該 Bean 存在對應的 URL,則新增該處理器
if (!ObjectUtils.isEmpty(urls)) {
// 呼叫父類的方法,往 `handlerMap` 中新增註冊器
registerHandler(urls, beanName);
}
}
if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
}
}
- 從 Spring 上下文獲取所有 Object 型別的 Bean 的名稱們
- 遍歷所有的 Bean ,逐個註冊
- 獲得 Bean 對應的 URL 們,呼叫
determineUrlsForHandler(String beanName)
抽象方法,交由子類實現,詳情見BeanNameUrlHandlerMapping
- 如果該 Bean 存在對應的 URL,則新增該處理器,呼叫父類的方法,往
handlerMap
中新增註冊器
- 獲得 Bean 對應的 URL 們,呼叫
BeanNameUrlHandlerMapping
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
,繼承 AbstractDetectingUrlHandlerMapping 抽象類,基於 Bean 的名字來自動探測的 HandlerMapping 實現類
使用示例
<!-- 定義一個 helloController Bean,實現了 Controller 介面 -->
<bean id="/hello.form" class="com.fullmoon.study.controller.HelloController"/>
和 SimpleUrlHandlerMapping 不同,只需要設定它的 beanName 以 /
開頭就好了,會被 BeanNameUrlHandlerMapping 探測到
determineUrlsForHandler
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
// 如果是以 / 開頭,新增到 urls
if (beanName.startsWith("/")) {
urls.add(beanName);
}
// 獲得 beanName 的別名們,如果以 / 開頭,則新增到 urls
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
}
邏輯很簡單,如果 Bean 的名稱或者別名是以 /
開頭,則會作為一個 url
返回,父類則會將該 Bean 作為一個處理器
總結
在 Spring MVC 處理請求的過程中,需要通過 HandlerMapping 元件會為請求找到合適的 HandlerExecutionChain
處理器執行鏈,包含處理器(handler
)和攔截器們(interceptors
),該元件體系結構如下:
本文就黃色框中的內容進行了分析,基於 URL 進行匹配。如果你接觸 Spring MVC 較早,可能見過 SimpleUrlHandlerMapping
和 BeanNameUrlHandlerMapping
中的使用示例的配置方式。當然,目前這種方式已經基本不用了,被 @RequestMapping
等註解的方式所取代。不過,Spring MVC 內建的一些路徑匹配,還是使用這種方式。
相對來說邏輯比較簡單,如果你有一個 Controller
或者 HttpRequestHandler
介面的實現類,有以下兩種方式將其設定為處理器,可以處理請求
- 配置 SimpleUrlHandlerMapping 型別的 HandlerMapping 物件,往它的
Map<String, Object> urlMap
中新增url
與Controller 實現類
的對映就好了 - 配置 BeanNameUrlHandlerMapping 型別的 HandlerMapping 物件,設定
Controller 實現類
的beanName
為以/
開頭的名稱就好了,它會探測到,將這個 Bean 的beanName
作為url
,將Controller 實現類
作為處理器
至此,HandlerMapping 元件就分析到這裡了,相信你對 HandlerMapping 元件有了一個深入的瞭解,更加的清楚 Spring MVC 是如何處理器請求的
HandlerMapping 元件返回的
HandlerExecutionChain
處理器執行鏈,包含處理器(handler
)和攔截器們(interceptors
),那麼這個處理器是被誰呼叫的呢?因為不同的 HandlerMapping 實現類返回的處理器型別可能不一樣,如何執行這個處理器,這部分工作都交由 HandlerAdapter 元件(處理器的介面卡)來完成
這裡我們就把處理器理解為
HandlerMethod
處理器物件吧,因為我們平時使用最多的方式就是通過@RequestMapping
註解來標註某個方法處理對應的請求別慌,接下來分析 HandlerAdapter 元件不會特別複雜?
參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》