Spring MVC 源碼分析 - HandlerMapping 組件(四)之 AbstractUrlHandlerMapping


參考 知識星球芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄

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

Spring 版本:5.1.14.RELEASE

該系列其他文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》

HandlerMapping 組件

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

  • handler 處理器是 Object 類型,可以將其理解成 HandlerMethod 對象(例如我們使用最多的 @RequestMapping 注解所標注的方法會解析成該對象),包含了方法的所有信息,通過該對象能夠執行該方法

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

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

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

  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 ,例如下圖:

構造方法

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

本文就黃色框中的內容進行了分析,基於 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 源碼分析》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM