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

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


04-SpringMVC请求过程分析(一)_第1张图片
DispatcherServletProcess.png

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


04-SpringMVC请求过程分析(一)_第2张图片
DoService.png

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

/**
     * Process the actual dispatching to the handler.
     * 

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. *

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.
         * 

Detects include request URL if called within a RequestDispatcher include. *

E.g.: servlet mapping = "/*"; request URI = "/test/a" -> "/test/a". *

E.g.: servlet mapping = "/"; request URI = "/test/a" -> "/test/a". *

E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a". *

E.g.: servlet mapping = "/test"; request URI = "/test" -> "". *

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 matches = new ArrayList<>(); // 这里是从urlLookup中获取RequestMappingInfo,一般来说url不带参数({xxx})的请求都可以从这里读到 List 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 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 mappings, List 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 matches = getMatchingPatterns(lookupPath);
        return (!matches.isEmpty() ?
                new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher,
                        this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions) : null);
}

public List getMatchingPatterns(String lookupPath) {
        List 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 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的过程。
由于本人能力有限,难免会有表述不清或错误的地方,还希望各位不吝指教,大家共同学习,一起进步。

你可能感兴趣的:(04-SpringMVC请求过程分析(一))