Spring MVC之RequestMappingHandlerMapping匹配

       對於RequestMappingHandlerMapping,使用Spring的同學基本都不會陌生,該類的作用有兩個:

  • 通過request查找對應的HandlerMethod,即當前request具體是由Controller中的哪個方法進行處理;
  • 查找當前系統中的Interceptor,將其與HandlerMethod封裝爲一個HandlerExecutionChain。

本文主要講解RequestMappingHandlerMapping是如何獲取HandlerMethod和Interceptor,並且將其封裝爲HandlerExecutionChain的。

1.整體封裝結構

       RequestMappingHandlerMapping實現了HandlerMapping接口,該接口的主要方法如下:

public interface HandlerMapping {
    // 通過request獲取HandlerExecutionChain對象
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

       這裏我們直接看RequestMappingHandlerMapping是如何實現該接口的:

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) 
    throws Exception {
    // 通過request獲取具體的處理bean,這裏handler可能有兩種類型:HandlerMethod和String。
    // 如果是String類型,那麼就在BeanFactory中查找該String類型的bean,需要注意的是,返回的
    // bean如果是需要使用RequestMappingHandlerAdapter處理,那麼也必須是HandlerMethod類型的
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        // 如果找不到處理方法,則獲取自定義的默認handler
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    if (handler instanceof String) {
        // 如果獲取的handler是String類型的,則在當前BeanFactory中獲取該名稱的bean,
        // 並將其作爲handler返回
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    // 獲取當前系統中配置的Interceptor,將其與handler一起封裝爲一個HandlerExecutionChain
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    // 這裏CorsUtils.isCorsRequest()方法判斷的是當前請求是否爲一個跨域的請求,如果是一個跨域的請求,
    // 則將跨域相關的配置也一併封裝到HandlerExecutionChain中
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = 
            this.globalCorsConfigSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? 
            globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

       從上面的代碼可以看出,對於HandlerExecutionChain的獲取,RequestMappingHandlerMapping首先會獲取當前request對應的handler,然後將其與Interceptor一起封裝爲一個HandlerExecutionChain對象。這裏在進行封裝的時候,Spring會對當前request是否爲跨域請求進行判斷,如果是跨域請求,則將相關的跨域配置封裝到HandlerExecutionChain中,關於跨域請求,讀者可以閱讀跨域資源共享 CORS 詳解

2. 獲取HandlerMethod

       關於RequestMappingHandlerMapping是如何獲取handler的,其主要在getHandlerInternal()方法中,如下是該方法的源碼:

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 獲取當前request的URI
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    if (logger.isDebugEnabled()) {
        logger.debug("Looking up handler method for path " + lookupPath);
    }
    // 獲取註冊的Mapping的讀鎖
    this.mappingRegistry.acquireReadLock();
    try {
        // 通過path和request查找具體的HandlerMethod
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        if (logger.isDebugEnabled()) {
            if (handlerMethod != null) {
                logger.debug("Returning handler method [" + handlerMethod + "]");
            } else {
                logger.debug("Did not find handler method for [" + lookupPath + "]");
            }
        }
        // 如果獲取到的bean是一個String類型的,則在BeanFactory中查找該bean,
        // 並將其封裝爲一個HandlerMethod對象
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    } finally {
        // 釋放當前註冊的Mapping的讀鎖
        this.mappingRegistry.releaseReadLock();
    }
}

       上述方法中,其首先會獲取當前request的uri,然後通過uri查找HandlerMethod,並且在最後,會判斷獲取到的HandlerMethod中的bean是否爲String類型的,如果是,則在當前BeanFactory中查找該名稱的bean,並且將其封裝爲HandlerMethod對象。這裏我們直接閱讀lookupHandlerMethod()方法:

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, 
      HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 通過uri直接在註冊的RequestMapping中獲取對應的RequestMappingInfo列表,需要注意的是,
    // 這裏進行查找的方式只是通過url進行查找,但是具體哪些RequestMappingInfo是匹配的,還需要進一步過濾
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        // 對獲取到的RequestMappingInfo進行進一步過濾,並且將過濾結果封裝爲一個Match列表
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // 如果無法通過uri進行直接匹配,則對所有的註冊的RequestMapping進行匹配,這裏無法通過uri
        // 匹配的情況主要有三種:
        // ①在RequestMapping中定義的是PathVariable,如/user/detail/{id};
        // ②在RequestMapping中定義了問號表達式,如/user/?etail;
        // ③在RequestMapping中定義了*或**匹配,如/user/detail/**
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), 
            matches, request);
    }

    if (!matches.isEmpty()) {
        // 對匹配的結果進行排序,獲取相似度最高的一個作爲結果返回,這裏對相似度的判斷時,
        // 會判斷前兩個是否相似度是一樣的,如果是一樣的,則直接拋出異常,如果不相同,
        // 則直接返回最高的一個
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        matches.sort(comparator);
        if (logger.isTraceEnabled()) {
            logger.trace("Found " + matches.size() 
                         + " matching mapping(s) for [" + lookupPath + "] : " + matches);
        }
        // 獲取匹配程度最高的一個匹配結果
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            // 如果匹配結果不止一個,首先會判斷是否是跨域請求,如果是,
            // 則返回PREFLIGHT_AMBIGUOUS_MATCH,如果不是,則會判斷前兩個匹配程度是否相同,
            // 如果相同則拋出異常
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                throw new IllegalStateException("Ambiguous handler methods mapped for" 
                    + " HTTP path '" + request.getRequestURL() + "': {" + m1 
                    + ", " + m2 + "}");
            }
        }
        // 這裏主要是對匹配結果的一個處理,主要包含對傳入參數和返回的MediaType的處理
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    } else {
        // 如果匹配結果是空的,則對所有註冊的Mapping進行遍歷,判斷當前request具體是哪種情況導致
        // 的無法匹配:①RequestMethod無法匹配;②Consumes無法匹配;③Produces無法匹配;
        // ④Params無法匹配
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), 
            lookupPath, request);
    }
}

       這裏對於結果的匹配,首先會通過uri進行直接匹配,如果能匹配到,則在匹配結果中嘗試進行RequestMethod,Consumes和Produces等配置的匹配;如果通過uri不能匹配到,則直接對所有定義的RequestMapping進行匹配,這裏主要是進行正則匹配,如果能匹配到。如果能夠匹配到,則對匹配結果按照相似度進行排序,並且對前兩個結果相似度進行比較,如果相似度一樣,則拋出異常,如果不一樣,則返回相似度最高的一個匹配結果。如果無法獲取到匹配結果,則對所有的匹配結果進行遍歷,判斷當前request具體是哪一部分參數無法匹配到結果。對於匹配結果的獲取,主要在addMatchingMappings()方法中,這裏我們繼續閱讀該方法的源碼:

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, 
      HttpServletRequest request) {
    for (T mapping : mappings) {
        T match = getMatchingMapping(mapping, request);
        if (match != null) {
            matches.add(new Match(match, 
                this.mappingRegistry.getMappings().get(mapping)));
        }
    }
}

       對於RequestMapping的匹配,這裏邏輯比較簡單,就是對所有的RequestMappingInfo進行遍歷,然後將request分別於每個RequestMappingInfo進行匹配,如果匹配上了,其返回值就不爲空,最後將所有的匹配結果返回。如下是getMatchingMapping()方法的源碼(其最終調用的是RequestMappingInfo.getMatchingCondition()方法):

@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
    // 判斷request請求的類型是否與當前RequestMethod匹配
    RequestMethodsRequestCondition methods = 
        this.methodsCondition.getMatchingCondition(request);
    // 判斷request請求的參數是否與RequestMapping中params參數配置的一致
    ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
    // 判斷request請求的headers是否與RequestMapping中headers參數配置的一致
    HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
    // 判斷request的請求體類型是否與RequestMapping中配置的consumes參數配置的一致
    ConsumesRequestCondition consumes = 
        this.consumesCondition.getMatchingCondition(request);
    // 判斷當前RequestMapping將要返回的請求體類型是否與request中Accept的header指定的一致
    ProducesRequestCondition produces = 
        this.producesCondition.getMatchingCondition(request);

    // 對於上述幾個判斷,如果匹配上了,那麼其返回值都不會爲空,因而這裏會對每個返回值都進行判斷,
    // 如果有任意一個爲空,則說明沒匹配上,那麼就返回null
    if (methods == null || params == null || headers == null 
        || consumes == null || produces == null) {
        return null;
    }

    // 對於前面的匹配,都是一些靜態屬性的匹配,其中最重要的uri的匹配,主要是正則匹配,
    // 就是在下面這個方法中進行的
    PatternsRequestCondition patterns = 
        this.patternsCondition.getMatchingCondition(request);
    // 如果URI沒匹配上,則返回null
    if (patterns == null) {
        return null;
    }

    // 這裏主要是對用戶自定義的匹配條件進行匹配
    RequestConditionHolder custom = 
        this.customConditionHolder.getMatchingCondition(request);
    if (custom == null) {
        return null;
    }

    // 如果上述所有條件都匹配上了,那麼就將匹配結果封裝爲一個RequestMappingInfo返回
    return new RequestMappingInfo(this.name, patterns, methods, params, headers, 
        consumes, produces, custom.getCondition());
}

        可以看到,對於一個RequestMapping的匹配,主要包括:RequestMethod,Params,Headers,Consumes,Produces,Uri和自定義條件的匹配,如果這幾個條件都匹配上了,才能表明當前RequestMapping與request匹配上了。

3. Interceptor的封裝

      關於Inteceptor的封裝,由前述第一點可以看出,其主要在getHandlerExecutionChain()方法中,如下是該方法的源碼:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, 
        HttpServletRequest request) {
    // 將當前handler封裝到HandlerExecutionChain對象中
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
        (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    // 獲取當前request的URI,用於MappedInterceptor的匹配
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    // 對當前所有註冊的Interceptor進行遍歷,如果其是MappedInterceptor類型,則調用其matches()
    // 方法,判斷當前Interceptor是否能夠應用於該request,如果可以,則添加到HandlerExecutionChain中
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        } else {
            // 如果當前Interceptor不是MappedInterceptor類型,則直接將其添加到
            // HandlerExecutionChain中
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

       對於攔截器,理論上,Spring是會將所有的攔截器都進行一次調用,對於是否需要進行攔截,都是用戶自定義實現的。這裏如果對於URI有特殊的匹配,可以使用MappedInterceptor,然後實現其matches()方法,用於判斷當前MappedInterceptor是否能夠應用於當前request。

4. 小結

       本文首先講解了Spring是如何通過request進行匹配,從而找到具體處理當前請求的RequestMapping的,然後講解了Spring是如何封裝Interceptor,將HandlerMethod和Interceptor封裝爲一個HandlerExecutionChain的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章