04-SpringMVC请求过程分析(一)

经过前面的铺垫我们开始分析SpringMVC的请求过程,由于整个过程较为复杂,本篇我们只讨论到请求寻址,后面的HandlerMethod调用及返回值处理下次再来分析。因为网上已经有很多流程图了,这里我就不再画流程图了,我们使用Spring官方描述的DispatcherServlet的处理流程来展开分析。


DispatcherServletProcess.png

我们将上图我们对照源码来看


DoService.png

DispatcherServlet在接收到请求之后会将WebApplicatinContext和当前request绑定,并且将localeResolver、themeResolver、themeSource(其实是WebApplicatinContext)绑定在request中。接下来会判断是否是转发过来的请求,如果是则会绑定FlashMap相关属性。最后到达DispatcherServlet的核心doDispatch方法。我们来着重分析这个方法。核心代码如下

/**
     * 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 {
    // 判断是否是上传文件请求
    processedRequest = checkMultipart(request);
    multipartRequestParsed = (processedRequest != request);   
    // Determine handler for the current request.
    // 请求寻址,返回HandlerExecutionChain 
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
    } 
    // Determine handler adapter for the current request.
    // 通过HandlerMapping寻找合适的适配器
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // 执行HandlerMapping中配置的拦截器的前置处理逻辑
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    // Actually invoke the handler.
    // 根据request中的请求路径匹配配置过的HandlerMapping并解析请求中的参数将其绑定在request中,随后与HandlerMethod中的方法绑定并调用HandlerMethod方法
    // 得到ModelAndView以供后面解析(RequestBody等类型的返回结果会直接返回数据)
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    // 如果返回了视图名称,这里会去拼接视图全路径(prefix+viewName+suffix)
    applyDefaultViewName(processedRequest, mv);
    // 执行HandlerMapping中配置的拦截器的后置处理逻辑
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    ..............................................
}

上面这段代码粗略解释了每个方法都做了什么,由于现在大多使用Restful风格的接口,所有在后面的分析中我们会以Restful风格的请求url为例来分析。下面我们以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个方法,其中前两个为传统的API风格,最后一个是Restful风格。本文将以第三个方法为例来分析请求过程。
当用户发起请求经过DispatcherServlet的一系列处理后到达getHandlerAdapter方法,这个方法是整个处理过程中的关键,涉及到url寻址及请求路径参数解析。关键代码如下

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 这里的handlerMappings就是初始化时加入的BeanNameUrlHandlerMapping和RequestMappingHandlerMapping
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                // 之前说过BeanNameUrlHandlerMapping是将Bean名称与请求url做匹配,基本不会使用这个HandlerMapping,这里会通过RequestMappingHandlerMapping来处理
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
}

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 获取Handler,下面详细分析
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        // 如果返回的是beanName,则通过IOC容器获取bean
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }
        // 将HandlerMethod封装成HandlerExecutionChain,后面会详细分析
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        return executionChain;
}
    
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 处理url,主要是将配置的servletmapping前缀从url中去除,该方法比较简单,我们不详细分析,只接用源码的注释来说明
        /**
         * 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 {
            // 这里就开始真正的映射handlerMethod并解析url参数,下面详细分析
            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,一般来说url不带参数({xxx})的请求都可以从这里读到
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        // 如果在urlLookup中已经拿到了mapping,直接进行匹配操作
        if (directPathMatches != null) {
            // 下面详细分析
            addMatchingMappings(directPathMatches, matches, request);
        }
        // 如果没有从urlLookup中得到,则通过mappingLookup来获取mapping
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            // 下面详细分析
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
        // 已经匹配到的话会对能匹配到的mapping进行排序,得到最佳匹配项
        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                // 匹配项超过1个会做一次判断,默认还是第一个结果
                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 + "}");
                }
            }
            // 将最佳匹配项的handlerMethod与request绑定
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            // 将最佳匹配项的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是在初始化过程中能和当前url匹配的所有RequestMappingInfo,每个mapping会封装有当前mapping能处理的mappingName、params、methods、paths等参数
        for (T mapping : mappings) {
            // 调用RequestMappingInfo中的getMatchingCondition来获取RequestMappingInfo
            T match = getMatchingMapping(mapping, request);
            if (match != null) {
                // 将RequestMappingInfo封装成Match
                matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
            }
        }
}

喝口水休息休息,本篇文章可能篇幅较长,但此时已经接近终点。希望大家坚持一下,下面我们继续。

public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
        // 获取当前RequestMappingInfo的各种Condition属性(初始化HandlerMapping的时候创建的)
        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;
        }
        // 通过patternsCondition来匹配request。下面专门分析
        PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
        if (patterns == null) {
            return null;
        }
        // 自定义的Condition,一般为空的
        RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
        if (custom == null) {
            return null;
        }
        // 根据上面生成的patterns和custom以及其他默认属性创建新的RequestMappingInfo并返回
        return new RequestMappingInfo(this.name, patterns,
                methods, params, headers, consumes, produces, custom.getCondition());
}

public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
        // 和上面getHandlerInternal方法中一样,是用来处理ServletMapping和请求路径用的
        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) {
            // 获取匹配结果,下面单独分析
            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;
        }
        // 这里会调用doMatch方法执行真正的匹配逻辑,通过这么久的学习过程我们应该熟悉看到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) {
        // 这里会将匹配模板切分成数组,本例中是["helloController","sayHello2","{guest}"]
        String[] pattDirs = tokenizePattern(pattern);
        // 这里将实际url切分成数组,本例中是["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;
            }
            // 这里会进行Ant风格匹配。
            // 注意后面在handlerAdapter处理时还会调用一次这个方法,在这个方法里面会解析url参数并将其绑定在request中
            if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
                return false;
            }
            pattIdxStart++;
            pathIdxStart++;
        }
        // 循环结束后如果请求路径数组全部结束说明已经匹配完了
        if (pathIdxStart > pathIdxEnd) {
            // Path is exhausted, only match if rest of pattern is * or **'s
            // 模板数组也匹配结束,判断模板和请求路径是否均以'/'结尾。不是返回true
            // 此时得到的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;
    }

至此已经匹配到mapping就是/helloController/sayHello2/{guest},此时会从mappingLookup中获取对应的HandlerMethod,然后将其封装成Match并返回HandlerMethod。最后在我们分析的第一个方法getHandler中会将得到的HandlerMethod封装成HandlerExecutionChain,最后返回给DispatcherServlet。

// 封装过程很简单,就是将handler保存下来,就不展开说明了
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

现在我们分析完了url寻址获取HandlerMethod的全过程,下篇文章我们来分析获取HandlerAdapter和调用Handler的过程。
由于本人能力有限,难免会有表述不清或错误的地方,还希望各位不吝指教,大家共同学习,一起进步。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容