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

浙公网安备 33010602011771号