美文网首页
04-SpringMVC请求过程分析(一)

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

作者: AcientFish | 来源:发表于2020-02-13 12:33 被阅读0次

    经过前面的铺垫我们开始分析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的过程。
    由于本人能力有限,难免会有表述不清或错误的地方,还希望各位不吝指教,大家共同学习,一起进步。

    相关文章

      网友评论

          本文标题:04-SpringMVC请求过程分析(一)

          本文链接:https://www.haomeiwen.com/subject/nvmkfhtx.html