SpringMVC原理(2)-目标方法是怎么被找到的

231 阅读6分钟

上一篇写到文件上传请求的原理。接下来就是获取Handler的原理了

本文分析的问题:目标方法是怎么被找到的

核心方法mappedHandler = getHandler(processedRequest):返回HandlerExecutionChain对象(包含目标方法、拦截器链)

getHandler()

 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
     if (this.handlerMappings != null) {
         for (HandlerMapping mapping : this.handlerMappings) {
             HandlerExecutionChain handler = mapping.getHandler(request);
             if (handler != null) {
                 return handler;
             }
         }
     }
     return null;
 }

遍历所有的HandlerMapping调用其getHandler()来拿到HandlerExecutionChain对象

5个HandlerMapping,在最后总结里有介绍各自的作用

image.png

HandlerMapping的作用就是根据请求找到对应的Handler

我们都是在controller接口中然后声明方法来做请求处理的,所以会由这个类处理 RequestMappingHandlerMapping(AbstractHandlerMapping 是它的父类):

RequestMappingHandlerMapping就是用来处理标注了xxxMapping注解的。基于注解来映射请求到处理器方法

 private Object defaultHandler;
 
 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
     // 根据当前请求拿到对应的 Handler 
     Object handler = getHandlerInternal(request);
     // 没拿到就给一个默认的 Handler
     if (handler == null) {
         handler = getDefaultHandler();
     }
     if (handler == null) {
         return null;
     }
     // Bean name or resolved handler?
     if (handler instanceof String) {
         String handlerName = (String) handler;
         handler = obtainApplicationContext().getBean(handlerName);
     }
 
     // 根据拿到的 Handler 和 当前请求得到一个 HandlerExecutionChain
     HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
 
     if (logger.isTraceEnabled()) {
         logger.trace("Mapped to " + handler);
     }
     else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
         logger.debug("Mapped to " + executionChain.getHandler());
     }
 
     // 处理跨域
     if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
         CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
         CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
         config = (config != null ? config.combine(handlerConfig) : handlerConfig);
         executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
     }
 
     return executionChain;
 }

获取处理器对象-getHandlerInternal()

拿到目标方法

最终会来到AbstractHandlerMethodMapping#getHandlerInternal

 // 路径帮助器
 private UrlPathHelper urlPathHelper = new UrlPathHelper();
 
 String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
 
 protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
     // 1、使用路径帮助器拿到请求中的请求路径
     String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
     request.setAttribute(LOOKUP_PATH, lookupPath);
     this.mappingRegistry.acquireReadLock();
     try {
         // 2、通过请求路径拿到 HandlerMethod (核心)
         HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
         return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
     }
     finally {
         this.mappingRegistry.releaseReadLock();
     }
 }

流程分析:

  1. getLookupPathForRequest():使用路径帮助器拿到请求中的请求路径
  2. lookupHandlerMethod():通过请求路径拿到HandlerMethod(核心)

根据当前请求得到路径getLookupPathForRequest()

就是调用底层api得到请求路径:request.getContextPath()request.getRequestURI()

public String getLookupPathForRequest(HttpServletRequest request) {
    // 得到路径
    String pathWithinApp = getPathWithinApplication(request);
    // Always use full path within current servlet context?
    if (this.alwaysUseFullPath) {
        return pathWithinApp;
    }
    // Else, use path within current servlet mapping if applicable
    String rest = getPathWithinServletMapping(request, pathWithinApp);
    if (StringUtils.hasLength(rest)) {
        return rest;
    }
    else {
        return pathWithinApp;
    }
}


public String getPathWithinApplication(HttpServletRequest request) {
   String contextPath = getContextPath(request);
   String requestUri = getRequestUri(request);
   String path = getRemainingPath(requestUri, contextPath, true);
   if (path != null) {
      // Normal case: URI contains context path.
      return (StringUtils.hasText(path) ? path : "/");
   }
   else {
      return requestUri;
   }
}

根据请求路径找到对应处理器-lookupHandlerMethod()

AbstractHandlerMethodMapping#lookupHandlerMethod

 // RequestMappingInfo 集合 k:路径 v:RequestMappingInfo
 private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
 
 // HandlerMethod 集合 k:RequestMappingInfo v:HandlerMethod
 private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
 
 protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
     // 根据路径匹配到的集合
     List<Match> matches = new ArrayList<>();
 
     // 1,根据路径找到对应的 RequestMappingInfo 集合
     List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
     if (directPathMatches != null) {
         // 2,匹配到的集合  这里面有 HandlerMethod 对象
         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()) {
         // 3,从匹配到的集合中拿到第一个
         Match bestMatch = matches.get(0);
         if (matches.size() > 1) {
             Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
             matches.sort(comparator);
             bestMatch = matches.get(0);
             if (logger.isTraceEnabled()) {
                 logger.trace(matches.size() + " matching mappings: " + matches);
             }
             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();
                 String uri = request.getRequestURI();
                 throw new IllegalStateException(
                     "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
             }
         }
         request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
 
         handleMatch(bestMatch.mapping, lookupPath, request);
         return bestMatch.handlerMethod;
     }
     else {
         return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
     }
 }

流程分析:

  1. getMappingsByUrl():去 MappingRegistry注册中心)中根据当前路径找到对应的RequestMappingInfo对象

  2. addMatchingMappings():再根据找到的RequestMappingInfoMappingRegistry注册中心)找HandlerMethod然后封装为Match对象,添加到matches集合中

  3. 如果匹配到了多个,会根据排序规则拿到最佳匹配的

1、getMappingsByUrl()

MappingRegistry注册中心)中根据当前路径找到对应的RequestMappingInfo对象

 // RequestMappingInfo 集合 k:路径 v:RequestMappingInfo
 private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

 public List<T> getMappingsByUrl(String urlPath) {
     return this.urlLookup.get(urlPath);
 }

注意PathVariable类型是不会加到这个集合里的,具体逻辑在这里,有兴趣可以去看这两个方法: org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register()、org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#getDirectUrls()


注意:这里key是路径,value是RequestMappingInfo对象

image.png

2、addMatchingMappings()

再根据找到的RequestMappingInfoMappingRegistry注册中心)找HandlerMethod然后封装为Match对象,添加到matches集合中

 // HandlerMethod 集合 k:RequestMappingInfo v:HandlerMethod
 private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

 private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
     for (T mapping : mappings) {
         // 找到对应的 RequestMappingInfo(比如RESTful风格就会在这一步处理)
         T match = getMatchingMapping(mapping, request);
         if (match != null) {
             // 找到的集合,又会封装为 Match 对象
             matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
         }
     }
 }

 // 封装 RequestMappingInfo
 return new RequestMappingInfo(this.name, pathPatterns, patterns,
       methods, params, headers, consumes, produces, custom, this.options);

注意:这里的key是RequestMappingInfo对象,value是HandlerMethod

image.png

Match对象

image.png

3、最后如果找到了多个,会根据规则拿到最佳匹配的HandlerMethod对象返回给我们

HandlerMethod对象:

image.png

目标方法: HandlerMethod对象

image.png

获取处理器执行链对象-getHandlerExecutionChain()

// ======AbstractHandlerMappin.class========
 protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
     // 1、封装 HandlerExecutionChain 对象
     HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                                    (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

     // 2、利用路径帮助器拿到当前路径
     String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
     // 3、遍历所有的拦截器
     for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
         if (interceptor instanceof MappedInterceptor) {
             MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
             // 3、判断当前拦截器是否拦截此请求
             if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                 // 添加到目标对象中
                 chain.addInterceptor(mappedInterceptor.getInterceptor());
             }
         }
         else {
             chain.addInterceptor(interceptor);
         }
     }
     return chain;
 }
 
 // ============MappedInterceptor.class============
  public boolean matches(String lookupPath, PathMatcher pathMatcher) {
     PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher);
     // 当前路径是否在排除路径的列表中
     if (!ObjectUtils.isEmpty(this.excludePatterns)) {
         for (String pattern : this.excludePatterns) {
             if (pathMatcherToUse.match(pattern, lookupPath)) {
                 return false;
             }
         }
     }
     if (ObjectUtils.isEmpty(this.includePatterns)) {
         return true;
     }
     // 当前路径是否在要拦截器的路径列表中
     for (String pattern : this.includePatterns) {
         if (pathMatcherToUse.match(pattern, lookupPath)) {
             return true;
         }
     }
     return false;
 }

流程:

  1. 先把HandlerMethod封装到HandlerExecutionChain

  2. 利用路径帮助器拿到当前请求路径

  3. 遍历所有的拦截器并判断当前拦截器是否拦截当前请求,拦截的话就添加到我们的HandlerExecutionChain对象中,然后返回


HandlerExecutionChain对象

image.png

这两个拦截器是默认的拦截器,不用管

里面就3个东西:处理器、拦截器集合、当前执行到第几个拦截器的索引

到这一步也就拿到了HandlerExecutionChain对象,最后还会有跨域的处理,就把这个对象返回出去了。

跨域放在下一篇说。

总结

HandlerMethod

目标方法。我们通过 @xxxMapping 修饰的Controller中接口就是目标方法。

会通过 RequestMappingHanderMapping 处理器适配器找到它。

会通过 RequestMappingHandlerAdapter 处理器适配器执行。

MappingRegistry

MappingRegistry:映射的注册中心

路径会映射为RequestMappingInfo对象,RequestMappingInfo又和HandlerMethod所映射

所以流程就是先根据路径找到对应的RequestMappingInfo,再根据RequestMappingInfo找到对应的HandlerMethod目标方法

HandlerMapping

HandlerMapping:处理器映射

public interface HandlerMapping {

   String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";

   String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";

   String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

   String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

   String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

   String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

   String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

   String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

   // 返回此请求的 HandlerExecutionChain 对象(Handler、Interceptor)
   @Nullable
   HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}
image.png

1、RequestMappingHandlerMapping就是用来处理标注了@xxxMapping注解的。基于注解来映射请求到处理器方法(最常用

2、BeanNameUrlHandlerMapping:根据请求的URL路径与控制器Bean的名称进行匹配。如果URL路径与某个控制器的Bean名称相匹配,则该控制器将被选中来处理请求。

3、SimpleUrlHandlerMapping:通过直接配置URL路径与处理器的映射关系来处理请求。开发者可以在配置文件中指定哪些URL应该由哪些控制器处理,提供了比BeanNameUrlHandlerMapping更灵活的映射方式

4、WelcomePageHandlerMapping:用来处理欢迎页。比如访问 /就会跳转到index.html

5、RouterFunctionMapping:函数式编程风格的接口


@xxxMapping注解

image.png

RESTful风格原理

RESTful风格原理

如果定义了多个路径相同的方法但请求方式不同,它是怎么找到对应方法的?

那也就是说getMappingsByUrl()会根据路径找到多个RequestMappingInfo对象。比如这样:

image.png

最终会在getMatchingMapping()找到匹配的目标方法,核心就是根据不同的请求方式找不同的方法

利用RequestMappingInfo对象里的RequestMethodsRequestCondition对象做区分的

image.png

源码:org.springframework.web.servlet.mvc.method.RequestMappingInfo#getMatchingCondition