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)有很多流程圖了胎撤,這里我就不再畫流程圖了晓殊,我們使用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咪啡。下面專門分析
        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);
        // 真正做匹配的地方撤摸,下面專門分析
        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)系作者
  • 序言:七十年代末博肋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜂厅,更是在濱河造成了極大的恐慌匪凡,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掘猿,死亡現(xiàn)場(chǎng)離奇詭異病游,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)稠通,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門衬衬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人改橘,你說(shuō)我怎么就攤上這事滋尉。” “怎么了飞主?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵狮惜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我碌识,道長(zhǎng)碾篡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任筏餐,我火速辦了婚禮开泽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘魁瞪。我一直安慰自己穆律,他們只是感情好惠呼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著峦耘,像睡著了一般罢杉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贡歧,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音赋秀,去河邊找鬼利朵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛猎莲,可吹牛的內(nèi)容都是我干的绍弟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼著洼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼樟遣!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起身笤,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤豹悬,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后液荸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瞻佛,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年娇钱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伤柄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡文搂,死狀恐怖适刀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情煤蹭,我是刑警寧澤笔喉,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站疯兼,受9級(jí)特大地震影響然遏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吧彪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一待侵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姨裸,春花似錦秧倾、人聲如沸怨酝。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)农猬。三九已至,卻和暖如春售淡,著一層夾襖步出監(jiān)牢的瞬間斤葱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工揖闸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留揍堕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓汤纸,卻偏偏與公主長(zhǎng)得像衩茸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贮泞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容