SpringMVC源码解读 - HandlerMapping - RequestMappingHandlerMapping请求分发
AbstractHandlerMethodMapping实现接口getHandlerInternal,定义查找流程
RequestMappingInfoHandlerMapping根据RequestMappingInfo,细化匹配条件,并在匹配不到情况下,顽强的使用RequestCondition一再尝试匹配
虽然 RequestMappingHandlerMapping是受益方,但在这边什么都没做(就是在初始化时,根据@Controller,@RequestMapping注解生成RequestMappingInfo;并根据这两个注解判断是否目标Handler 实现isHandler)
AbstractHandlerMethodMapping实现接口getHandlerInternal
1. 使用UrlPathHelper查找request对应的path
2. 查找path对应的HandlerMethod
2.1 从urlMap中直接等值匹配查找匹配条件RequestMappingInfo
2.2 如果等值查找到匹配条件,将其添加到match条件中
2.3 如果没有找到匹配条件,使用所有的handlerMethod的RequestMappingInfo进行匹配
2.4 对匹配到的Match进行排序,取出最高优先级的Match,并核对是否是唯一的最高优先级
2.5 对匹配到条件,没有匹配到条件的两种情况,分别进行封装
3. 封装HandlerMethod,确保bean中存的是实例
// AbstractHandlerMethodMapping
实现接口getHandlerInternal
1 package org.springframework.web.servlet.handler 2 // AbstractHandlerMethodMapping<T> 3 /** 4 * Look up a handler method for the given request. 5 */ 6 @Override 7 protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { 8 // 就是request对应的url 9 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); 10 // 查找到处理器,这边的处理器会封装成HandlerMethod 11 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); 12 // 确保bean中存的是实例 13 return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null; 14 }
// AbstractHandlerMethodMapping
package org.springframework.web.servlet.handler; public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean { /** * Look up the best-matching handler method for the current request. * If multiple matches are found, the best match is selected. * @param lookupPath mapping lookup path within the current servlet mapping * @param request the current request * @return the best-matching handler method, or {@code null} if no match * @see #handleMatch(Object, String, HttpServletRequest) * @see #handleNoMatch(Set, String, HttpServletRequest) */ protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); // 从urlMap中直接等值匹配查找匹配条件RequestMappingInfo List<T> directPathMatches = this.urlMap.get(lookupPath); if (directPathMatches != null) { // addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // No choice but to go through all mappings // 没有匹配的情况下,遍历handlerMethods的全部匹配条件进行查找 addMatchingMappings(this.handlerMethods.keySet(), matches, request); } if (!matches.isEmpty()) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); Match bestMatch = matches.get(0); if (matches.size() > 1) { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); // 不能有相同的最优Match throw new IllegalStateException( "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); } } // 就是往request域中缓存url中解析出来的参数,mediaType等,这边RequestMappingHandlerMapping也覆写了一下 handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { // RequestMappingHandlerMapping return handleNoMatch(handlerMethods.keySet(), lookupPath, request); } } }
// AbstractHandlerMethodMapping
查找具体符合条件的RequestCondition
1 package org.springframework.web.servlet.handler; 2 public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean { 3 4 private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) { 5 for (T mapping : mappings) { 6 T match = getMatchingMapping(mapping, request); 7 if (match != null) { 8 matches.add(new Match(match, handlerMethods.get(mapping))); 9 } 10 } 11 }
// AbstractHandlerMethodMapping
1 /** 2 * Check if a mapping matches the current request and return a (potentially 3 * new) mapping with conditions relevant to the current request. 4 * @param mapping the mapping to get a match for 5 * @param request the current HTTP servlet request 6 * @return the match, or {@code null} if the mapping doesn't match 7 */ 8 protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);
我们来看看RequestMappingInfoHandlerMapping中的实现,从RequestMappingInfo中查找符合的RequestCondition
// RequestMappingInfoHandlerMapping
1 /** 2 * Check if the given RequestMappingInfo matches the current request and 3 * return a (potentially new) instance with conditions that match the 4 * current request -- for example with a subset of URL patterns. 5 * @return an info in case of a match; or {@code null} otherwise. 6 */ 7 @Override 8 protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) { 9 return info.getMatchingCondition(request); 10 }
// AbstractHandlerMethodMapping
1 /** 2 * Invoked when a matching mapping is found. 3 * @param mapping the matching mapping 4 * @param lookupPath mapping lookup path within the current servlet mapping 5 * @param request the current request 6 */ 7 protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) { 8 request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath); 9 }
RequestMappingInfoHandlerMapping中又对其进行了覆写,具体是干啥用的,等看了HandlerAdaptor再说吧
1 /** 2 * Expose URI template variables, matrix variables, and producible media types in the request. 3 * @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE 4 * @see HandlerMapping#MATRIX_VARIABLES_ATTRIBUTE 5 * @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 6 */ 7 @Override 8 protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) { 9 super.handleMatch(info, lookupPath, request); 10 11 Set<String> patterns = info.getPatternsCondition().getPatterns(); 12 String bestPattern = patterns.isEmpty() ? lookupPath : patterns.iterator().next(); 13 request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern); 14 15 Map<String, String> uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath); 16 Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables); 17 request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables); 18 19 if (isMatrixVariableContentAvailable()) { 20 request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(request, uriVariables)); 21 } 22 23 if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) { 24 Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes(); 25 request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes); 26 } 27 }
1 /** 2 * Invoked when no matching mapping is not found. 3 * @param mappings all registered mappings 4 * @param lookupPath mapping lookup path within the current servlet mapping 5 * @param request the current request 6 * @throws ServletException in case of errors 7 */ 8 protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request) 9 throws Exception { 10 11 return null; 12 }
RequestMappingInfoHandlerMapping,覆写,不死心,再匹配一次
// RequestMappingInfoHandlerMapping
1 /** 2 * Iterate all RequestMappingInfos once again, look if any match by URL at 3 * least and raise exceptions accordingly. 4 * @throws HttpRequestMethodNotSupportedException if there are matches by URL 5 * but not by HTTP method 6 * @throws HttpMediaTypeNotAcceptableException if there are matches by URL 7 * but not by consumable/producible media types 8 */ 9 @Override 10 protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos, 11 String lookupPath, HttpServletRequest request) throws ServletException { 12 13 Set<String> allowedMethods = new LinkedHashSet<String>(4); 14 15 Set<RequestMappingInfo> patternMatches = new HashSet<RequestMappingInfo>(); 16 Set<RequestMappingInfo> patternAndMethodMatches = new HashSet<RequestMappingInfo>(); 17 18 for (RequestMappingInfo info : requestMappingInfos) { 19 if (info.getPatternsCondition().getMatchingCondition(request) != null) { 20 patternMatches.add(info); 21 if (info.getMethodsCondition().getMatchingCondition(request) != null) { 22 patternAndMethodMatches.add(info); 23 } 24 else { 25 for (RequestMethod method : info.getMethodsCondition().getMethods()) { 26 allowedMethods.add(method.name()); 27 } 28 } 29 } 30 } 31 32 if (patternMatches.isEmpty()) { 33 return null; 34 } 35 else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) { 36 throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods); 37 } 38 39 Set<MediaType> consumableMediaTypes; 40 Set<MediaType> producibleMediaTypes; 41 Set<String> paramConditions; 42 43 if (patternAndMethodMatches.isEmpty()) { 44 consumableMediaTypes = getConsumableMediaTypes(request, patternMatches); 45 producibleMediaTypes = getProducibleMediaTypes(request, patternMatches); 46 paramConditions = getRequestParams(request, patternMatches); 47 } 48 else { 49 consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches); 50 producibleMediaTypes = getProducibleMediaTypes(request, patternAndMethodMatches); 51 paramConditions = getRequestParams(request, patternAndMethodMatches); 52 } 53 54 if (!consumableMediaTypes.isEmpty()) { 55 MediaType contentType = null; 56 if (StringUtils.hasLength(request.getContentType())) { 57 try { 58 contentType = MediaType.parseMediaType(request.getContentType()); 59 } 60 catch (IllegalArgumentException ex) { 61 throw new HttpMediaTypeNotSupportedException(ex.getMessage()); 62 } 63 } 64 throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes)); 65 } 66 else if (!producibleMediaTypes.isEmpty()) { 67 throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes)); 68 } 69 else if (!CollectionUtils.isEmpty(paramConditions)) { 70 String[] params = paramConditions.toArray(new String[paramConditions.size()]); 71 throw new UnsatisfiedServletRequestParameterException(params, request.getParameterMap()); 72 } 73 else { 74 return null; 75 } 76 }