SpringMvc如何将Url 映射到 RequestMapping (二)

昨天简单分析了Springmvc 中 RequestMapping 配置的url和请求url之间的匹配规则。今天详细的跟踪一下一个请求url如何映射到Controller的对应方法上

一、入口 org.springframework.web.servlet.DispatcherServlet.doDispatch(HttpServletRequest, HttpServletResponse)

 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2         HttpServletRequest processedRequest = request;
 3         HandlerExecutionChain mappedHandler = null;
 4         boolean multipartRequestParsed = false;
 5 
 6         WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 7 
 8         try {
 9             ModelAndView mv = null;
10             Exception dispatchException = null;
11 
12             try {
            // 检查是否文件上传 即 contentType = "multipart/###"
13 processedRequest = checkMultipart(request); 14 multipartRequestParsed = (processedRequest != request); 15 16 // 进入获取 mappedHandler 17 mappedHandler = getHandler(processedRequest); 18 if (mappedHandler == null || mappedHandler.getHandler() == null) { 19 noHandlerFound(processedRequest, response); 20 return; 21 } 22 23 // Determine handler adapter for the current request. 24 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 25 26 // Process last-modified header, if supported by the handler. 27 String method = request.getMethod(); 28 boolean isGet = "GET".equals(method); 29 if (isGet || "HEAD".equals(method)) { 30 long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
34 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 35 return; 36 } 37 } 39 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 40 return; 41 } 43 // Actually invoke the handler. 44 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 45 46 if (asyncManager.isConcurrentHandlingStarted()) { 47 return; 48 } 49 50 applyDefaultViewName(processedRequest, mv); 51 mappedHandler.applyPostHandle(processedRequest, response, mv); 52 }61 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 62 }84 }

从doDispatch方法入口(DispatcherServlet 处理request请求入口)

org.springframework.web

  servlet.DispatcherServlet.doDispatch(HttpServletRequest, HttpServletResponse)

    servlet.DispatcherServlet.getHandler(HttpServletRequest)

      servlet.handler.AbstractHandlerMapping.getHandler(HttpServletRequest)

        servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(HttpServletRequest)

          servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(String, HttpServletRequest)

            servlet.mvc.method.RequestMappingInfoHandlerMapping.handleMatch(RequestMappingInfo, String, HttpServletRequest)

解析url和requestMapping 直接的映射关系,这个时候我们发现spring将 RequestMapping定义的url都存储在 MappingRegistry 这个类中,接下来先解析一下 这个类具体储存了那些数据。

mappingLookup = new LinkedHashMap<T, HandlerMethod>()
内部数据结构 {[/test]}=public java.lang.String xiaolang.TestController.test()
urlLookup = new LinkedMultiValueMap<String, T>()
内部数据结构 /test=[{[/test]}] 
根据调试内容可以分析
urlLookup 的key 和请求url进行完全匹配或者正则匹配 获取 定义的RequestMapping
mappingLookup  通过获取的RequestMapping 匹配对应的Controller 
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
        // 如果匹配成功 则加载match addMatchingMappings(directPathMatches, matches, request); }
if (matches.isEmpty()) { // No choice but to go through all mappings...
       // 没有全路径匹配 则进行正则匹配
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); } if (!matches.isEmpty()) {
        // 将左右匹配到的规则进行 排序 (排序规则请参考上一篇随笔) Comparator
<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); Match bestMatch = matches.get(0);
       // 如果 匹配规则大于 1个 即两个 patten 同时满足 url 且 匹配度一致
       // 则抛出异常
if (matches.size() > 1) { if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); } }
       // 根据 最佳匹配的 url中 将url中的参数等信息封装到 handlermethod 中 此时已经找到了最佳Controller.method handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); }
接下来我们跟踪一下具体解析逻辑:

1、完全匹配 

this.mappingRegistry.getMappingsByUrl(lookupPath); - > urlLookup.get(urlPath)

从mappingRegistry 查看是否有完全匹配的路径,(在上一篇文章中分析过,完全匹配优先级最高)

如果匹配成功过

  则 addMatchingMappings(directPathMatches, matches, request); -> mappingLookup(次数省略了部分校验判断)

  从mappingLookup中找到对应Controller的method

2、正则匹配 

addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);

如果没有匹配成功,则对所有的定义的url进行正则匹配(此处只进行规则匹配)

3、将所有匹配结果进行优先级排序(排序规则在上一篇文章中已经详细描述)

获取最佳匹配规则 和 第二匹配规则进行比较,如果最佳匹配规则和第二匹配规则优先级相同,即spring无法确定应该使用哪个Controller进行处理,此时程序抛出异常将提示错误

4、封装请求结果

 handleMatch(bestMatch.mapping, lookupPath, request);
将请求结果进行封装,包括请求URL参数、等相关信息。

5、根据请求结果返回对应的Controller SpringBean

handlerMethod.createWithResolvedBean()

 

综合上面debug 调试

1、我们已经找到了最佳匹配的method 且发现,spring 在启动时已经将 url method 等相关映射信息保存在了 MappingRegistry 中

2、如果出现规则匹配度一致的url,系统会提示错误,所以尽量避免 定义比较广泛匹配的RequestMapping 

 

posted @ 2019-04-20 15:08  阿朗~  阅读(434)  评论(0)    收藏  举报