04-SpringMVC請(qǐng)求過(guò)程分析(一)

經(jīng)過(guò)前面的鋪墊我們開(kāi)始分析SpringMVC的請(qǐng)求過(guò)程,由于整個(gè)過(guò)程較為復(fù)雜,本篇我們只討論到請(qǐng)求尋址,后面的HandlerMethod調(diào)用及返回值處理下次再來(lái)分析。因?yàn)榫W(wǎng)上已經(jīng)有很多流程圖了,這里我就不再畫(huà)流程圖了,我們使用Spring官方描述的DispatcherServlet的處理流程來(lái)展開(kāi)分析。


DispatcherServletProcess.png

我們將上圖我們對(duì)照源碼來(lái)看


DoService.png

DispatcherServlet在接收到請(qǐng)求之后會(huì)將WebApplicatinContext和當(dāng)前request綁定,并且將localeResolver、themeResolver、themeSource(其實(shí)是WebApplicatinContext)綁定在request中。接下來(lái)會(huì)判斷是否是轉(zhuǎn)發(fā)過(guò)來(lái)的請(qǐng)求,如果是則會(huì)綁定FlashMap相關(guān)屬性。最后到達(dá)DispatcherServlet的核心doDispatch方法。我們來(lái)著重分析這個(gè)方法。核心代碼如下

/**
     * Process the actual dispatching to the handler.
     * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
     * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
     * to find the first that supports the handler class.
     * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
     * themselves to decide which methods are acceptable.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception in case of any kind of processing failure
     */
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 判斷是否是上傳文件請(qǐng)求
    processedRequest = checkMultipart(request);
    multipartRequestParsed = (processedRequest != request);   
    // Determine handler for the current request.
    // 請(qǐng)求尋址,返回HandlerExecutionChain 
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
    } 
    // Determine handler adapter for the current request.
    // 通過(guò)HandlerMapping尋找合適的適配器
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // 執(zhí)行HandlerMapping中配置的攔截器的前置處理邏輯
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    // Actually invoke the handler.
    // 根據(jù)request中的請(qǐng)求路徑匹配配置過(guò)的HandlerMapping并解析請(qǐng)求中的參數(shù)將其綁定在request中,隨后與HandlerMethod中的方法綁定并調(diào)用HandlerMethod方法
    // 得到ModelAndView以供后面解析(RequestBody等類型的返回結(jié)果會(huì)直接返回?cái)?shù)據(jù))
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    // 如果返回了視圖名稱,這里會(huì)去拼接視圖全路徑(prefix+viewName+suffix)
    applyDefaultViewName(processedRequest, mv);
    // 執(zhí)行HandlerMapping中配置的攔截器的后置處理邏輯
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    ..............................................
}

上面這段代碼粗略解釋了每個(gè)方法都做了什么,由于現(xiàn)在大多使用Restful風(fēng)格的接口,所有在后面的分析中我們會(huì)以Restful風(fēng)格的請(qǐng)求url為例來(lái)分析。下面我們以http://localhost:8080/app/helloController/sayHello2/haha為例,Controller代碼如下

@RestController
@RequestMapping("/helloController")
public class HelloController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/sayHello")
    public String sayHello(@RequestParam String guests){
        helloService.sayHello(guests);
        return "success";
    }

    @GetMapping("/sayHellos")
    public String sayHello(@RequestParam String[] guests){
        Arrays.asList(guests).forEach( guest -> {
            helloService.sayHello(guest);
        });
        return "success";
    }

    @GetMapping("/sayHello2/{guest}")
    public String sayHello2(@PathVariable String guest){
        helloService.sayHello(guest);
        return "success";
    }
}

可以看出總共有3個(gè)方法,其中前兩個(gè)為傳統(tǒng)的API風(fēng)格,最后一個(gè)是Restful風(fēng)格。本文將以第三個(gè)方法為例來(lái)分析請(qǐng)求過(guò)程。
當(dāng)用戶發(fā)起請(qǐng)求經(jīng)過(guò)DispatcherServlet的一系列處理后到達(dá)getHandlerAdapter方法,這個(gè)方法是整個(gè)處理過(guò)程中的關(guān)鍵,涉及到url尋址及請(qǐng)求路徑參數(shù)解析。關(guān)鍵代碼如下

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 這里的handlerMappings就是初始化時(shí)加入的BeanNameUrlHandlerMapping和RequestMappingHandlerMapping
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                // 之前說(shuō)過(guò)BeanNameUrlHandlerMapping是將Bean名稱與請(qǐng)求url做匹配,基本不會(huì)使用這個(gè)HandlerMapping,這里會(huì)通過(guò)RequestMappingHandlerMapping來(lái)處理
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
}

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 獲取Handler,下面詳細(xì)分析
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        // 如果返回的是beanName,則通過(guò)IOC容器獲取bean
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }
        // 將HandlerMethod封裝成HandlerExecutionChain,后面會(huì)詳細(xì)分析
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        return executionChain;
}
    
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 處理url,主要是將配置的servletmapping前綴從url中去除,該方法比較簡(jiǎn)單,我們不詳細(xì)分析,只接用源碼的注釋來(lái)說(shuō)明
        /**
         * Return the path within the servlet mapping for the given request,
         * i.e. the part of the request's URL beyond the part that called the servlet,
         * or "" if the whole URL has been used to identify the servlet.
         * <p>Detects include request URL if called within a RequestDispatcher include.
         * <p>E.g.: servlet mapping = "/*"; request URI = "/test/a" -> "/test/a".
         * <p>E.g.: servlet mapping = "/"; request URI = "/test/a" -> "/test/a".
         * <p>E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a".
         * <p>E.g.: servlet mapping = "/test"; request URI = "/test" -> "".
         * <p>E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "".
         * @param request current HTTP request
         * @return the path within the servlet mapping, or ""
         */
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        this.mappingRegistry.acquireReadLock();
        try {
            // 這里就開(kāi)始真正的映射handlerMethod并解析url參數(shù),下面詳細(xì)分析
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
}   

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<>();
        // 這里是從urlLookup中獲取RequestMappingInfo,一般來(lái)說(shuō)url不帶參數(shù)({xxx})的請(qǐng)求都可以從這里讀到
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        // 如果在urlLookup中已經(jīng)拿到了mapping,直接進(jìn)行匹配操作
        if (directPathMatches != null) {
            // 下面詳細(xì)分析
            addMatchingMappings(directPathMatches, matches, request);
        }
        // 如果沒(méi)有從urlLookup中得到,則通過(guò)mappingLookup來(lái)獲取mapping
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            // 下面詳細(xì)分析
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
        // 已經(jīng)匹配到的話會(huì)對(duì)能匹配到的mapping進(jìn)行排序,得到最佳匹配項(xiàng)
        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                // 匹配項(xiàng)超過(guò)1個(gè)會(huì)做一次判斷,默認(rèn)還是第一個(gè)結(jié)果
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException(
                            "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
            // 將最佳匹配項(xiàng)的handlerMethod與request綁定
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            // 將最佳匹配項(xiàng)的mapping與request綁定
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
}

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
        // 這里的mappings是在初始化過(guò)程中能和當(dāng)前url匹配的所有RequestMappingInfo,每個(gè)mapping會(huì)封裝有當(dāng)前mapping能處理的mappingName、params、methods、paths等參數(shù)
        for (T mapping : mappings) {
            // 調(diào)用RequestMappingInfo中的getMatchingCondition來(lái)獲取RequestMappingInfo
            T match = getMatchingMapping(mapping, request);
            if (match != null) {
                // 將RequestMappingInfo封裝成Match
                matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
            }
        }
}

喝口水休息休息,本篇文章可能篇幅較長(zhǎng),但此時(shí)已經(jīng)接近終點(diǎn)。希望大家堅(jiān)持一下,下面我們繼續(xù)。

public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
        // 獲取當(dāng)前RequestMappingInfo的各種Condition屬性(初始化HandlerMapping的時(shí)候創(chuàng)建的)
        RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
        ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
        HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
        ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
        ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);

        if (methods == null || params == null || headers == null || consumes == null || produces == null) {
            return null;
        }
        // 通過(guò)patternsCondition來(lái)匹配request。下面專門(mén)分析
        PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
        if (patterns == null) {
            return null;
        }
        // 自定義的Condition,一般為空的
        RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
        if (custom == null) {
            return null;
        }
        // 根據(jù)上面生成的patterns和custom以及其他默認(rèn)屬性創(chuàng)建新的RequestMappingInfo并返回
        return new RequestMappingInfo(this.name, patterns,
                methods, params, headers, consumes, produces, custom.getCondition());
}

public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
        // 和上面getHandlerInternal方法中一樣,是用來(lái)處理ServletMapping和請(qǐng)求路徑用的
        String lookupPath = this.pathHelper.getLookupPathForRequest(request);
        // 真正做匹配的地方,下面專門(mén)分析
        List<String> matches = getMatchingPatterns(lookupPath);
        return (!matches.isEmpty() ?
                new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher,
                        this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions) : null);
}

public List<String> getMatchingPatterns(String lookupPath) {
        List<String> matches = new ArrayList<>();
        // this.patterns就是RequestMapping中配置的url,本例中是/helloController/sayHello2/{guest}
        for (String pattern : this.patterns) {
            // 獲取匹配結(jié)果,下面單獨(dú)分析
            String match = getMatchingPattern(pattern, lookupPath);
            if (match != null) {
                matches.add(match);
            }
        }
        if (matches.size() > 1) {
            matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
        }
        return matches;
}

private String getMatchingPattern(String pattern, String lookupPath) {
        if (pattern.equals(lookupPath)) {
            return pattern;
        }
        // 這里會(huì)調(diào)用doMatch方法執(zhí)行真正的匹配邏輯,通過(guò)這么久的學(xué)習(xí)過(guò)程我們應(yīng)該熟悉看到doXXX方法意味著什么!
        if (this.pathMatcher.match(pattern, lookupPath)) {
            return pattern;
        }
        if (this.useTrailingSlashMatch) {
            if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
                return pattern +"/";
            }
        }
        return null;
}

protected boolean doMatch(String pattern, String path, boolean fullMatch,
            @Nullable Map<String, String> uriTemplateVariables) {
        // 這里會(huì)將匹配模板切分成數(shù)組,本例中是["helloController","sayHello2","{guest}"]
        String[] pattDirs = tokenizePattern(pattern);
        // 這里將實(shí)際url切分成數(shù)組,本例中是["helloController","sayHello2","haha"]
        String[] pathDirs = tokenizePath(path);
        int pattIdxStart = 0;
        int pattIdxEnd = pattDirs.length - 1;
        int pathIdxStart = 0;
        int pathIdxEnd = pathDirs.length - 1;

        // Match all elements up to the first **
        while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
            String pattDir = pattDirs[pattIdxStart];
            if ("**".equals(pattDir)) {
                break;
            }
            // 這里會(huì)進(jìn)行Ant風(fēng)格匹配。
            // 注意后面在handlerAdapter處理時(shí)還會(huì)調(diào)用一次這個(gè)方法,在這個(gè)方法里面會(huì)解析url參數(shù)并將其綁定在request中
            if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
                return false;
            }
            pattIdxStart++;
            pathIdxStart++;
        }
        // 循環(huán)結(jié)束后如果請(qǐng)求路徑數(shù)組全部結(jié)束說(shuō)明已經(jīng)匹配完了
        if (pathIdxStart > pathIdxEnd) {
            // Path is exhausted, only match if rest of pattern is * or **'s
            // 模板數(shù)組也匹配結(jié)束,判斷模板和請(qǐng)求路徑是否均以'/'結(jié)尾。不是返回true
            // 此時(shí)得到的pattern是/helloController/sayHello2/{guest}
            if (pattIdxStart > pattIdxEnd) {
                return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator));
            }
            if (!fullMatch) {
                return true;
            }
            if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
                return true;
            }
            for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
                if (!pattDirs[i].equals("**")) {
                    return false;
                }
            }
            return true;
        }
        // .....................后面還有很多.............................
        return true;
    }

至此已經(jīng)匹配到mapping就是/helloController/sayHello2/{guest},此時(shí)會(huì)從mappingLookup中獲取對(duì)應(yīng)的HandlerMethod,然后將其封裝成Match并返回HandlerMethod。最后在我們分析的第一個(gè)方法getHandler中會(huì)將得到的HandlerMethod封裝成HandlerExecutionChain,最后返回給DispatcherServlet。

// 封裝過(guò)程很簡(jiǎn)單,就是將handler保存下來(lái),就不展開(kāi)說(shuō)明了
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

現(xiàn)在我們分析完了url尋址獲取HandlerMethod的全過(guò)程,下篇文章我們來(lái)分析獲取HandlerAdapter和調(diào)用Handler的過(guò)程。
由于本人能力有限,難免會(huì)有表述不清或錯(cuò)誤的地方,還希望各位不吝指教,大家共同學(xué)習(xí),一起進(jìn)步。

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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