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

月圓吖發表於2020-12-17

該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀

Spring 版本:5.2.4.RELEASE

該系列其他文件請檢視:《精盡 Spring MVC 原始碼分析 - 文章導讀》

HandlerMapping 元件

HandlerMapping 元件,請求的處理器匹配器,負責為請求找到合適的 HandlerExecutionChain 處理器執行鏈,包含處理器(handler)和攔截器們(interceptors

  • handler 處理器是 Object 型別,可以將其理解成 HandlerMethod 物件(例如我們使用最多的 @RequestMapping 註解所標註的方法會解析成該物件),包含了方法的所有資訊,通過該物件能夠執行該方法

  • HandlerInterceptor 攔截器對處理請求進行增強處理,可用於在執行方法前、成功執行方法後、處理完成後進行一些邏輯處理

由於 HandlerMapping 元件涉及到的內容比較多,考慮到內容的排版,所以將這部分內容拆分成了四個模組,依次進行分析:

HandlerMapping 元件(四)之 AbstractUrlHandlerMapping

先來回顧一下HandlerMapping 介面體系的結構:

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

《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,依次註冊處理器

  1. 如果非延遲載入,並且 handler 為 String 型別,並且還是單例,則去獲取 String 對應的 Bean 物件,resolvedHandler
  2. handlerMap中獲得 urlPath 對應的處理器
  3. 如果該路徑已存在對應的處理器,但是不是當前 resolvedHandler 物件,則丟擲異常
  4. 否則,該路徑不存在對應的處理器,則將當前 resolvedHandler 處理器儲存
    1. 如果是 / 根路徑,則設定 resolvedHandlerrootHandler
    2. 否則,如果是 /* 路徑,則設定為預設處理器 defaultHandler(在父類中)
    3. 否則,新增到 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;
}
  1. 獲得請求路徑

  2. 呼叫 lookupHandler(String urlPath, HttpServletRequest request) 方法,獲得處理器,詳情見下文

  3. 如果找不到處理器,則使用 rootHandlerdefaultHandler 處理器

    1. 如果是/根路徑,則使用 rootHandler 處理器
    2. 否則,使用 defaultHandler 預設處理器
    3. 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
    4. 呼叫validateHandler(Object handler, HttpServletRequest request),對處理器進行校驗,空方法,暫無子類實現該方法
    5. 呼叫 buildPathExposingHandler方法,建立 HandlerExecutionChain 處理器執行鏈,賦值給handler處理器,詳情見下文
  4. 返回請求對應的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;
}
  1. 情況一

    1. handlerMap 中,直接匹配處理器
    2. 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
    3. 校驗處理器,空方法,暫無子類實現,暫時忽略
    4. 建立處理器,直接返回,這裡是 HandlerExecutionChain 型別,呼叫 buildPathExposingHandler 方法,詳情見下文
  2. 情況二

    1. Pattern 匹配合適的,並新增到 matchingPatterns
    2. 獲得首個匹配(最優)的結果 bestMatch
    3. 獲得 bestMatch 對應的處理器,如果獲得不到,丟擲異常
    4. 如果找到的處理器是 String 型別,則從容器中找到該 beanName 對應的 Bean 作為處理器
    5. 校驗處理器,空方法,暫無子類實現,暫時忽略
    6. 獲得請求最匹配的路徑pathWithinMapping
    7. 獲得匹配的路徑引數集合uriTemplateVariables
    8. 建立處理器,直接返回,這裡是 HandlerExecutionChain 型別,呼叫 buildPathExposingHandler 方法,詳情見下文
  3. 都不匹配則返回 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;
}
  1. 建立 HandlerExecutionChain 型別的處理器物件
  2. 新增 PathExposingHandlerInterceptor 攔截器,用於暴露 bestMatchingPattern 屬性到請求中
  3. 新增 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;
}
  1. 獲得請求路徑
  2. 模式匹配,若匹配,則返回 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 ,例如下圖:

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

構造方法

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.formHelloController 設定到 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());
    }
}
  1. 從 Spring 上下文獲取所有 Object 型別的 Bean 的名稱們
  2. 遍歷所有的 Bean ,逐個註冊
    1. 獲得 Bean 對應的 URL 們,呼叫determineUrlsForHandler(String beanName)抽象方法,交由子類實現,詳情見 BeanNameUrlHandlerMapping
    2. 如果該 Bean 存在對應的 URL,則新增該處理器,呼叫父類的方法,往 handlerMap 中新增註冊器

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),該元件體系結構如下:

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

本文就黃色框中的內容進行了分析,基於 URL 進行匹配。如果你接觸 Spring MVC 較早,可能見過 SimpleUrlHandlerMappingBeanNameUrlHandlerMapping 中的使用示例的配置方式。當然,目前這種方式已經基本不用了,被 @RequestMapping 等註解的方式所取代。不過,Spring MVC 內建的一些路徑匹配,還是使用這種方式。

相對來說邏輯比較簡單,如果你有一個 Controller 或者 HttpRequestHandler 介面的實現類,有以下兩種方式將其設定為處理器,可以處理請求

  • 配置 SimpleUrlHandlerMapping 型別的 HandlerMapping 物件,往它的 Map<String, Object> urlMap 中新增 urlController 實現類的對映就好了
  • 配置 BeanNameUrlHandlerMapping 型別的 HandlerMapping 物件,設定 Controller 實現類beanName 為以 / 開頭的名稱就好了,它會探測到,將這個 Bean 的 beanName 作為 url,將 Controller 實現類 作為處理器

至此,HandlerMapping 元件就分析到這裡了,相信你對 HandlerMapping 元件有了一個深入的瞭解,更加的清楚 Spring MVC 是如何處理器請求的

HandlerMapping 元件返回的 HandlerExecutionChain 處理器執行鏈,包含處理器(handler)和攔截器們(interceptors),那麼這個處理器是被誰呼叫的呢?

因為不同的 HandlerMapping 實現類返回的處理器型別可能不一樣,如何執行這個處理器,這部分工作都交由 HandlerAdapter 元件(處理器的介面卡)來完成

這裡我們就把處理器理解為 HandlerMethod 處理器物件吧,因為我們平時使用最多的方式就是通過 @RequestMapping 註解來標註某個方法處理對應的請求

別慌,接下來分析 HandlerAdapter 元件不會特別複雜?

參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》

相關文章