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

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

04-SpringMVC请求过程分析(一)
文章图片
DispatcherServletProcess.png 我们将上图我们对照源码来看

04-SpringMVC请求过程分析(一)
文章图片
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); .............................................. }

【04-SpringMVC请求过程分析(一)】上面这段代码粗略解释了每个方法都做了什么,由于现在大多使用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的过程。
由于本人能力有限,难免会有表述不清或错误的地方,还希望各位不吝指教,大家共同学习,一起进步。

    推荐阅读