Spring MVC 核心類: HandlerMapping(二)

上一篇
Spring MVC 核心類: HandlerMapping(一)

2. 詳細(xì)說(shuō)明

2.1 AbstractHandlerMapping

HandlerMapping 實(shí)現(xiàn)的基礎(chǔ)類。這個(gè)基礎(chǔ)類不支持暴露PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE 屬性。這個(gè)屬性的暴露取決于具體的子類實(shí)現(xiàn),例如典型的通過(guò)URL映射來(lái)實(shí)現(xiàn)。

這個(gè)超類定義了默認(rèn)handler、排序、攔截器的基本實(shí)現(xiàn)。

2.1.1 默認(rèn)handler設(shè)置:

默認(rèn)為null。主要用于當(dāng)無(wú)法匹配到handler時(shí),返回這個(gè)具體指定的handler。

    /**
     * Set the default handler for this handler mapping.
     * This handler will be returned if no specific mapping was found.
     * <p>Default is {@code null}, indicating no default handler.
     */
    public void setDefaultHandler(@Nullable Object defaultHandler) {
        this.defaultHandler = defaultHandler;
    }
2.1.2 順序

默認(rèn)為最低優(yōu)先級(jí),主要通過(guò)實(shí)現(xiàn) Spring 框架中的 Ordered 接口來(lái)支撐。

    private int order = Ordered.LOWEST_PRECEDENCE;

    /**
     * Specify the order value for this HandlerMapping bean.
     * <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
     * @see org.springframework.core.Ordered#getOrder()
     */
    public void setOrder(int order) {
        this.order = order;
    }
2.1.3 URL 處理

通過(guò) UrlPathHelper ,把URL的處理能力轉(zhuǎn)交出去,從而實(shí)現(xiàn)各種URL處理的能力,例如實(shí)現(xiàn)ANT模式的URL處理方式。同時(shí)也提供了便利的設(shè)置 UrlPathHelper 屬性的方法,方便直接配置:

  1. 可以設(shè)置一直使用全路徑匹配;
  2. 可以設(shè)置URL的decode操作;
  3. 可以設(shè)置是否移除URL中分號(hào)的內(nèi)容

通過(guò) UrlPathHelper, Spring 框架可以提供靈活的URL處理方式,后續(xù) HandlerMapping 子類實(shí)現(xiàn)中會(huì)使用到具體的實(shí)例來(lái)完成對(duì)應(yīng)的功能。

2.1.4 攔截器(interceptor)

1.首先可以設(shè)置攔截器

設(shè)置匹配這個(gè)Handler映射的所有攔截器。目前支持的攔截器類型有HandlerInterceptor、WebRequestInterceptor、MappedInterceptor 三種。通過(guò)路徑匹配找到對(duì)應(yīng)攔截器處理。

    /**
     * Set the interceptors to apply for all handlers mapped by this handler mapping.
     * <p>Supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor.
     * Mapped interceptors apply only to request URLs that match its path patterns.
     * Mapped interceptor beans are also detected by type during initialization.
     * @param interceptors array of handler interceptors
     * @see #adaptInterceptor
     * @see org.springframework.web.servlet.HandlerInterceptor
     * @see org.springframework.web.context.request.WebRequestInterceptor
     */
    public void setInterceptors(Object... interceptors) {
        this.interceptors.addAll(Arrays.asList(interceptors));
    }

2.初始化過(guò)程

首先 Spring 框架提供了在初始化或者是正式使用的擴(kuò)展點(diǎn)。支持用戶自定義增加或者其他想要的操作來(lái)處理將要被配置的攔截器列表。

再次是在Spring Bean 容器中查找 MappedInterceptor 所有實(shí)例,包含父 Spring Bean 容器中的所有 MappedInterceptor 實(shí)例。同時(shí)把查找到的MappedInterceptor添加到adaptedInterceptors列表中。當(dāng)然這是默認(rèn)實(shí)現(xiàn),如果你想改變這種策略,Spring 框架是支持通過(guò)重寫 detectMappedInterceptors 方法來(lái)擴(kuò)展。

最后把把 interceptors 列表中的攔截器添加到 adaptedInterceptors中。放入代理的攔截器列表前會(huì)通過(guò) MappedInterceptor 具體實(shí)現(xiàn)類型進(jìn)行包裝:如果直接實(shí)現(xiàn)HandlerInterceptor不包裝;如果實(shí)現(xiàn)WebRequestInterceptor就使用 WebRequestHandlerInterceptorAdapter包裝。在默認(rèn)實(shí)現(xiàn)中僅僅支持這兩種類型的處理,如果需要可以自己擴(kuò)展。

通過(guò)上面的處理,adaptedInterceptors裝載來(lái)所有的攔截器。

    /**
     * Initializes the interceptors.
     * @see #extendInterceptors(java.util.List)
     * @see #initInterceptors()
     */
    @Override
    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.adaptedInterceptors);
        initInterceptors();
    }
2.1.5 獲取Handler(getHandler)

根據(jù)Request,查找對(duì)應(yīng)的Handler。當(dāng)沒(méi)有匹配的Handler時(shí),使用指定的默認(rèn)Handler。

    /**
     * Look up a handler for the given request, falling back to the default
     * handler if no specific one is found.
     * @param request current HTTP request
     * @return the corresponding handler instance, or the default handler
     * @see #getHandlerInternal
     */
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // 如果是使用的BeanName,從spring容器中獲取
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

        ...

        return executionChain;
    }

實(shí)現(xiàn)中有兩次擴(kuò)展,可以供子類使用:

  1. getHandlerInternal :
    • 真正的執(zhí)行通過(guò)指定的Request查找對(duì)應(yīng)的Handler,當(dāng)無(wú)法匹配到對(duì)應(yīng)的Handler 時(shí),返回null。通過(guò)上面的 getHandler 方法的調(diào)用,我們可以看出,當(dāng)此方法返回 null 時(shí),會(huì)觸發(fā)默認(rèn) Handler 賦值。
    • 這個(gè)方法你可以返回一個(gè)已經(jīng)包裝好的HandlerExecutionChain。因?yàn)?HandlerExecutionChain 中已經(jīng)存在了相關(guān)攔截器的配置,所以這些攔截器會(huì)動(dòng)態(tài)和HandlerMapping中配置的攔截器進(jìn)行合并。也就是說(shuō)靜態(tài)的攔截器會(huì)合并到已經(jīng)提供的 HandlerExecutionChain 中。
  2. getHandlerExecutionChain
    • getHandler 方法在調(diào)用getHandlerInternal后獲取到具體的handler后調(diào)用該方法。主要用于構(gòu)建一個(gè) Handler 處理器的處理鏈路。在構(gòu)建這個(gè)處理鏈時(shí),會(huì)選擇合適的攔截器。
    • 構(gòu)建一個(gè)默認(rèn)標(biāo)準(zhǔn)的HandlerExecutionChain包含指定的Handler、通用映射對(duì)應(yīng)的攔截器、以及和當(dāng)前請(qǐng)求URL匹配的MappedInterceptor。攔截器的順序依賴注冊(cè)順序的先后。如果需求,子類可以重寫這個(gè)順序來(lái)擴(kuò)展/重新構(gòu)建這個(gè)攔截器列表。
    • 這個(gè)方法支持用原始的Handler創(chuàng)建新的HandlerExecutionChain和傳入的HandlerExecutionChain兩種方式。
      如果僅僅是簡(jiǎn)單的在子類中增加一個(gè)攔截器,可以考慮調(diào)用super.getHandlerExecutionChain(handler, request),同時(shí)調(diào)用 HandlerExecutionChain 中的 addInterceptor方法。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

2.2 AbstractUrlHandlerMapping

用URL映射方式的基礎(chǔ)HandlerMapping實(shí)現(xiàn)類。本類提供了通過(guò)URL或者配置的具體的URL查找方式的HandlerMapping實(shí)現(xiàn)的基礎(chǔ)設(shè)施,對(duì)于后者,可以通過(guò)“alwaysUseFullPath”屬性來(lái)了解更多。

支持直接匹配,例如配置 “/test” 來(lái)映射 “/test”;或者使用ANT形式來(lái)匹配,例如配置“/t”來(lái)映射“/test”和“/team”,使用“/test/”來(lái)匹配所有“/test”的直接目錄下的URL,使用“/test/**”來(lái)匹配“/test”下面所有的URL包含子目錄下的鏈接。更多配置詳情,可以參考 AntPathMatcher 。

匹配過(guò)程中會(huì)查詢所有匹配路徑,同時(shí)找到最長(zhǎng)匹配作為最精確的匹配。

同時(shí)此基礎(chǔ)類定義了handlerMap,這個(gè)Map定義了URL映射關(guān)系。本基礎(chǔ)類提供了Map注冊(cè)的模版方法(registerHandler),供其子類調(diào)用。

2.2.1 設(shè)置根目錄對(duì)應(yīng)的Handler,即為“/”的handler。默認(rèn)情況為null
/**
 * Set the root handler for this handler mapping, that is,
 * the handler to be registered for the root path ("/").
 * <p>Default is {@code null}, indicating no root handler.
 */
public void setRootHandler(Object rootHandler) {
    this.rootHandler = rootHandler;
}

2.2.2 覆蓋getHandlerInternal 方法
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    Object handler = lookupHandler(lookupPath, request);
    // 當(dāng)通過(guò)URL路徑無(wú)法匹配時(shí),優(yōu)先處理根Handler,再使用默認(rèn)Handler。
    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;
        if ("/".equals(lookupPath)) {
            rawHandler = getRootHandler();
        }
        if (rawHandler == null) {
            rawHandler = getDefaultHandler();
        }
        if (rawHandler != null) {
            // Bean name or resolved handler?
            if (rawHandler instanceof String) {
                String handlerName = (String) rawHandler;
                rawHandler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(rawHandler, request);
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    …
    return handler;
}

2.2.3 查到Handler (lookupHandler)
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
    // 1. 如果可以通過(guò)URL直接映射,使用這個(gè)映射對(duì)應(yīng)的Handler
    Object handler = this.handlerMap.get(urlPath);
    if (handler != null) {
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        validateHandler(handler, request);
        return buildPathExposingHandler(handler, urlPath, urlPath, null);
    }

    // 2. 通過(guò)匹配進(jìn)行映射
    List<String> matchingPatterns = new ArrayList<String>();
    // 2.1 找到所有的可能的映射對(duì)應(yīng)的Handler
    for (String registeredPattern : this.handlerMap.keySet()) {
        if (getPathMatcher().match(registeredPattern, urlPath)) {
            matchingPatterns.add(registeredPattern);
        }
        else if (useTrailingSlashMatch()) {
            if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
                matchingPatterns.add(registeredPattern + "/");
            }
        }
    }

    // 2.2 決策最優(yōu)映射
    String bestMatch = null;
    Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
    if (!matchingPatterns.isEmpty()) {
        // 通過(guò)路徑比較器對(duì)所有匹配的映射進(jìn)行排序,最長(zhǎng)映射放在第一位
        Collections.sort(matchingPatterns, patternComparator);
        if (logger.isDebugEnabled()) {
            logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
        }
        // 返回第一個(gè)映射,即為最優(yōu)映射
        bestMatch = matchingPatterns.get(0);
    }
    if (bestMatch != null) {
        // 通過(guò)最優(yōu)的匹配路徑來(lái)找對(duì)應(yīng)的Handler,如果沒(méi)有找到對(duì)應(yīng)的Handler,報(bào)錯(cuò)
        handler = this.handlerMap.get(bestMatch);
        if (handler == null) {
            if (bestMatch.endsWith("/")) {
                handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
            }
            if (handler == null) {
                throw new IllegalStateException(
                        "Could not find handler for best pattern match [" + bestMatch + "]");
            }
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        validateHandler(handler, request);
        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
        Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
        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.isDebugEnabled()) {
            logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
        }
        return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
    }

    // No handler found...
    return null;
}

2.2.4 模版方法:registerHandler

通過(guò)給定的URL路徑,注冊(cè)指定的Handler。

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        ...
        Object resolvedHandler = handler;
        ...

        // 從HandlerMap中獲取URL路徑對(duì)應(yīng)的Handler
        Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            // 如果出現(xiàn)一個(gè)URL路徑兩個(gè)匹配的Handler的情況報(bào)錯(cuò)
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException(...);
            }
        }
        else {
            // 如果是根目錄的情況下,設(shè)置根目錄處理器
            if (urlPath.equals("/")) {
                ...
                setRootHandler(resolvedHandler);
            }
            // 如果是根目錄匹配的情況下,設(shè)置根目錄處理器
            else if (urlPath.equals("/*")) {
                ...
                setDefaultHandler(resolvedHandler);
            }
            // 其他情況下,設(shè)置URL路徑和處理器的映射
            else {
                this.handlerMap.put(urlPath, resolvedHandler);
                ...
            }
        }
    }

2.3 AbstractUrlHandlerMapping 子類

2.3.1 BeanNameUrlHandlerMapping

該類是 AbstractDetectingUrlHandlerMapping 的子類。在 AbstractDetectingUrlHandlerMapping 中定義了檢測(cè)所有Bean的基礎(chǔ)方法。其子類通過(guò)擴(kuò)展 determineUrlsForHandler 方法進(jìn)行判斷是否需要進(jìn)行URL注冊(cè)。

    protected void detectHandlers() throws BeansException {
        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.
        for (String beanName : beanNames) {
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                // URL paths found: Let's consider it a handler.
                registerHandler(urls, beanName);
            }
        }
        ...
    }

BeanNameUrlHandlerMapping 擴(kuò)展了 determineUrlsForHandler,定義通過(guò)Bean名稱來(lái)定義URL,通過(guò)配置Bean名稱的別名(以“/”開(kāi)頭)來(lái)確定對(duì)應(yīng)的URL:

    @Override
    protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = obtainApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }
2.3.2 SimpleUrlHandlerMapping

這個(gè)類是通過(guò)配置URL到對(duì)應(yīng)的處理器的Bean名稱的方式的HandlerMapping。支持URL到bean實(shí)例和URL到bean名稱兩種方式。使用Bean名稱映射時(shí),僅僅支持該Bean為單例模式,否則無(wú)法通過(guò)Bean名稱初始化。

配置URL到處理器Handler有兩種方式:

1.通過(guò)屬性文件配置,屬性文件的的樣子如下:

 /welcome.html=ticketController
 /show.html=ticketController

2.直接通過(guò)XML配置 urlMap

    /**
     * Set a Map with URL paths as keys and handler beans (or handler bean names)
     * as values. Convenient for population with bean references.
     * <p>Supports direct URL matches and Ant-style pattern matches. For syntax
     * details, see the {@link org.springframework.util.AntPathMatcher} javadoc.
     * @param urlMap map with URLs as keys and beans as values
     * @see #setMappings
     */
    public void setUrlMap(Map<String, ?> urlMap) {
        this.urlMap.putAll(urlMap);
    }

同時(shí)本類定義了處理URL集合的方法

    /**
     * 觸發(fā)解析URL配置
     */
    @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);
            });
            ...
        }
    }

綜上,這個(gè)類處理的核心是配置具體的URL到Controller的映射關(guān)系。當(dāng)然URL可以遵循 ANT 模式。

2.3.3 WelcomePageHandlerMapping

首先在構(gòu)建時(shí)定義默認(rèn)的Controller和根路徑處理Handler:

    WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
            ApplicationContext applicationContext, Optional<Resource> welcomePage,
            String staticPathPattern) {
        if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
            logger.info("Adding welcome page: " + welcomePage.get());
            setRootViewName("forward:index.html");
        }
        else if (welcomeTemplateExists(templateAvailabilityProviders,
                applicationContext)) {
            logger.info("Adding welcome page template: index");
            setRootViewName("index");
        }
    }
    
    private void setRootViewName(String viewName) {
        ParameterizableViewController controller = new ParameterizableViewController();
        controller.setViewName(viewName);
        setRootHandler(controller);
        setOrder(2);
    }

再次覆蓋 getHandlerInternal 方法,使用超類中通過(guò)URL匹配不到就判斷是否為根路徑同時(shí)使用根路徑對(duì)應(yīng)的處理Handler的邏輯,達(dá)到使用通用的歡迎頁(yè)的Controller的目的。

    @Override
    public Object getHandlerInternal(HttpServletRequest request) throws Exception {
        for (MediaType mediaType : getAcceptedMediaTypes(request)) {
            if (mediaType.includes(MediaType.TEXT_HTML)) {
                return super.getHandlerInternal(request);
            }
        }
        return null;
    }

綜上,這個(gè)類處理的核心是配置Index對(duì)應(yīng)的Controller為默認(rèn)的根處理Handler

下一篇:方法級(jí)別的映射分析
Spring MVC 核心類: HandlerMapping(三)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容