天天看點

SpringMVC--@RequestMapping的注冊及查找流程

介紹

一個@RequestMapping請求的流程如下

  1. 通過RequestMappingHandlerMapping傳回HandlerMethod這種類型的handler
  2. 找到能處理HandlerMethod的RequestMappingHandlerAdapter
  3. RequestMappingHandlerAdapter執行業務邏輯傳回ModelAndView

Spring容器啟動的時候,會将@RequestMapping中的資訊和要執行的方法存放在AbstractHandlerMethodMapping的内部類MappingRegistry中的5個成員變量中,其中的T為RequestMappingInfo,是對@RequestMapping注解資訊的封裝

class MappingRegistry {

  // MappingRegistration包含
  // 1. 映射資訊
  // 2. 映射資訊中的直接路徑(非模式化路徑)
  // 3. 映射名
  // 4. 對應的處理器方法
  
  // 分别為下面4個map的key
  // 這個隻是對4個key的封裝,隻在unregister方法中會使用
  private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

  // RequestMappingInfo -> HandlerMethod
  private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

  // 不包含通配符的url -> List<RequestMappingInfo>
  // 這裡為什麼是一個List呢?因為一個url有可能對應多個方法,即這些方法的@RequestMapping注解path屬性一樣,但是其他屬性不一樣
  private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

  // 映射名 -> HandlerMethod
  private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

  // HandlerMethod -> 跨域配置
  private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
}      

中最主要的是如下2個map,其餘的本節關聯不大,不再分析

urlLookup:儲存了如下映射關系

key=不包含通配符的url

value=List<RequestMappingInfo>

mappingLookup:

key=RequestMappingInfo

value=HandlerMethod

為什麼需要這2個map呢?先留着這個問題,我們繼續往後看

RequestMappingHandlerMapping的查找過程

RequestMappingHandlerMapping的繼承關系如下

SpringMVC--@RequestMapping的注冊及查找流程

HandlerMapping接口一個getHandler方法,用來根據請求的url找到對應的HandlerExecutionChain對象。

public interface HandlerMapping {

  @Nullable
  HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}      

而HandlerExecutionChain對象就是簡單的将url對應的Handler和攔截器封裝了一下

public class HandlerExecutionChain {

  private final Object handler;

  @Nullable
  private List<HandlerInterceptor> interceptorList;
}      

通過debug發現,整個查找過程隻是依次調用了如下3個方法

  1. AbstractHandlerMapping#getHandler
  2. AbstractHandlerMethodMapping#getHandlerInternal
  3. AbstractHandlerMethodMapping#lookupHandlerMethod

AbstractHandlerMapping#getHandler

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  // getHandlerInternal 是模闆方法,留給子類去實作
  Object handler = getHandlerInternal(request);
  if (handler == null) {
    // 沒有擷取到使用預設的handler
    handler = getDefaultHandler();
  }
  if (handler == null) {
    return null;
  }
  // Bean name or resolved handler?
  // 如果handler是string類型,則以它為名從容器中查找相應的bean
  if (handler instanceof String) {
    String handlerName = (String) handler;
    handler = obtainApplicationContext().getBean(handlerName);
  }

  // 拿到handler,和我們配置的攔截器,封裝成HandlerExecutionChain對象傳回
  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
  // 跨域配置
  if (CorsUtils.isCorsRequest(request)) {
    CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
    CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
    executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  }
  return executionChain;
}      

可以看到找handler的過程是交由子類去實作的。并将對這個請求适用的攔截器,跨域配置,handler封裝成HandlerExecutionChain傳回給DispatcherServlet

AbstractHandlerMethodMapping#getHandlerInternal

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  // 擷取請求路徑,作為查找路徑
  String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
  if (logger.isDebugEnabled()) {
    logger.debug("Looking up handler method for path " + lookupPath);
  }
  // 擷取讀鎖
  this.mappingRegistry.acquireReadLock();
  try {
    HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    if (logger.isDebugEnabled()) {
      if (handlerMethod != null) {
        logger.debug("Returning handler method [" + handlerMethod + "]");
      }
      else {
        logger.debug("Did not find handler method for [" + lookupPath + "]");
      }
    }
    // handlerMethod就是要執行的controller方法的封裝
    return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
  }
  finally {
    this.mappingRegistry.releaseReadLock();
  }
}      

這一步做的事情也不是很多,就是根據url找到對應的HandlerMethod

AbstractHandlerMethodMapping#lookupHandlerMethod

兜兜轉轉終于來到根據請求擷取相應的HandlerMethod對象了

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  // 比對結果清單
  List<Match> matches = new ArrayList<>();
  // 直接比對
  List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
  // 如果有比對的,就添加進比對清單中
  if (directPathMatches != null) {
    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));
    // 對多個比對結果進行排序
    matches.sort(comparator);
    if (logger.isTraceEnabled()) {
      logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
    }
    // 最佳比對
    Match bestMatch = matches.get(0);
    if (matches.size() > 1) {
      if (CorsUtils.isPreFlightRequest(request)) {
        return PREFLIGHT_AMBIGUOUS_MATCH;
      }
      Match secondBestMatch = matches.get(1);
      // 第一個元素和第二個元素進行比較
      if (comparator.compare(bestMatch, secondBestMatch) == 0) {
        // 結果為0,至少有2個最佳比對
        Method m1 = bestMatch.handlerMethod.getMethod();
        Method m2 = secondBestMatch.handlerMethod.getMethod();
        // 多個最佳比對,抛出異常
        throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
            request.getRequestURL() + "': {" + 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);
  }
}      

這個方法就涉及到我們最開始提到的那2個map。

​urlLookup​

key=不包含通配符的url

value=List<RequestMappingInfo>

​mappingLookup​

key=RequestMappingInfo

value=HandlerMethod

  1. 先根據url從urlLookup中查找對應的RequestMappingInfo,如果找到的List<RequestMappingInfo>不為空,則判斷其他比對條件是否符合
  2. 如果其他條件也有符合的,則不再周遊所有的RequestMappingInfo,否則周遊所有的RequestMappingInfo,因為考慮到有通配符形式的url必須周遊所有的RequestMappingInfo才能找出來符合條件的
  3. 如果最終找到的RequestMappingInfo有多個,則按照特定的規則找出一個最比對的,并傳回其對應的HandlerMethod

可能有小夥伴有很多疑惑,為什麼一個請求請求位址會有多個實作。我給你舉一個例子,有3個相同的handler方法,@RequestMapping中的其他屬性都相同,隻是method不同,這樣就有可能根據一個url找到3個RequestMappingInfo,這樣就有選擇一個最優RequestMappingInfo的過程

當然還有其他情況,比如一個url同時比對2個通配符路徑。

可以看出來查找的過程還是挺簡單的,接着我們就來分析一下注冊的過程

RequestMappingHandlerMapping的初始化過程

從上面的分析中我們可以得出結論,在Spring容器啟動後,RequestMappingInfo和HandlerMethod的映射關系已經被儲存到MappingRegistry對象中,那麼它是多會儲存的以及以何種形式儲存的?

通過檢視調用關系法相,MappingRegistry中各種映射關系的初始化在register()方法,是以我們隻需要在其register()方法上加一個斷點,檢視其調用鍊路即可

SpringMVC--@RequestMapping的注冊及查找流程
  1. RequestMappingHandlerMapping#afterPropertiesSet
  2. AbstractHandlerMethodMapping#afterPropertiesSet
  3. AbstractHandlerMethodMapping#initHandlerMethods
  4. AbstractHandlerMethodMapping#detectHandlerMethods
  5. AbstractHandlerMethodMapping#registerHandlerMethod

從RequestMappingInfoHandlerMapping可以看出AbstractHandlerMethodMapping中的泛型類為RequestMappingInfo

RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo>      

接着仔細分析這些方法

RequestMappingHandlerMapping#afterPropertiesSet

@Override
public void afterPropertiesSet() {
  // 建立映射資訊構造器配置,用于構造映射資訊RequestMappingInfo
  this.config = new RequestMappingInfo.BuilderConfiguration();
  this.config.setUrlPathHelper(getUrlPathHelper());
  this.config.setPathMatcher(getPathMatcher());
  this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
  this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
  this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
  // 設定内容協商管理器元件
  this.config.setContentNegotiationManager(getContentNegotiationManager());

  // 執行處理器方法的初始化邏輯
  super.afterPropertiesSet();
}      

主要做了一些初始化設定

AbstractHandlerMethodMapping#afterPropertiesSet

@Override
public void afterPropertiesSet() {
  initHandlerMethods();
}      

單純調用initHandlerMethods方法

AbstractHandlerMethodMapping#initHandlerMethods

protected void initHandlerMethods() {
  if (logger.isDebugEnabled()) {
    logger.debug("Looking for request mappings in application context: " + getApplicationContext());
  }
  
  // 擷取ApplicationContext中所有BeanName
  String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
      BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
      obtainApplicationContext().getBeanNamesForType(Object.class));

  for (String beanName : beanNames) {
    // 排除Scoped目标類型Bean
    if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
      Class<?> beanType = null;
      try {
        beanType = obtainApplicationContext().getType(beanName);
      }
      catch (Throwable ex) {
        // An unresolvable bean type, probably from a lazy bean - let's ignore it.
        if (logger.isDebugEnabled()) {
          logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
        }
      }
      // 如果擷取的BeanName類型不為空,且是一個處理器類型的Bean
      // 如果beanType上有Controller注解或者RequestMapping注解則是handler
      if (beanType != null && isHandler(beanType)) {
        detectHandlerMethods(beanName);
      }
    }
  }
  handlerMethodsInitialized(getHandlerMethods());
}      

從容器中拿到所有的beanName,排除Scope類型的bean,根據beanName得到beanType,如果一個beanType是一個handler,則調用AbstractHandlerMethodMapping#detectHandlerMethods方法

AbstractHandlerMethodMapping#detectHandlerMethods

注冊的重頭戲就在這裡了

protected void detectHandlerMethods(Object handler) {
  // 如果傳入的handler是String類型,則表示是一個BeanName,從上下文擷取
  Class<?> handlerType = (handler instanceof String ?
      obtainApplicationContext().getType((String) handler) : handler.getClass());

  if (handlerType != null) {
    Class<?> userType = ClassUtils.getUserClass(handlerType);
    // Method -> Method上對應的RequestMappingInfo
    Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
        (MethodIntrospector.MetadataLookup<T>) method -> {
          try {
            return getMappingForMethod(method, userType);
          }
          catch (Throwable ex) {
            throw new IllegalStateException("Invalid mapping on handler class [" +
                userType.getName() + "]: " + method, ex);
          }
        });
    if (logger.isDebugEnabled()) {
      logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
    }
    methods.forEach((method, mapping) -> {
      // 擷取真實的可執行的方法,因為上述查找邏輯在特殊情況下查找到的方法可能存在于代理上
      // 需要擷取非代理方法作為可執行方法調用
      Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
      registerHandlerMethod(handler, invocableMethod, mapping);
    });
  }
}      

就是擷取Handler上的Method,以及根據Method上@RequestMapping注解封裝的RequestMappingInfo,

最後根據Handler,Method,RequestMappingInfo将映射關系注冊到map中

AbstractHandlerMethodMapping#registerHandlerMethod

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
  this.mappingRegistry.register(mapping, handler, method);
}      

AbstractHandlerMethodMapping#MappingRegistry#register

public void register(T mapping, Object handler, Method method) {
  // 擷取寫鎖
  this.readWriteLock.writeLock().lock();
  try {
    // 把類和方法封裝為HandlerMethod
    HandlerMethod handlerMethod = createHandlerMethod(handler, method);
    // 保證方法映射唯一
    // 如果一個相同的url對應多個handlerMethod則會抛出異常
    assertUniqueMethodMapping(handlerMethod, mapping);
    // 向映射查找表中添加  映射資訊->對應的處理器方法
    this.mappingLookup.put(mapping, handlerMethod);

    if (logger.isInfoEnabled()) {
      logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
    }

    // 存儲 不帶統配符的url -> RequestMappingInfo 的映射關系
    List<String> directUrls = getDirectUrls(mapping);
    for (String url : directUrls) {
      this.urlLookup.add(url, mapping);
    }

    String name = null;
    // 命名政策不為空
    if (getNamingStrategy() != null) {
      // 通過命名政策擷取映射名
      name = getNamingStrategy().getName(handlerMethod, mapping);
      // 注冊到nameLookup
      addMappingName(name, handlerMethod);
    }

    // 擷取HandlerMethod對應的CORS配置
    CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
    if (corsConfig != null) {
      this.corsLookup.put(handlerMethod, corsConfig);
    }

    // 注冊跨域配置
    this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
  }
  finally {
    this.readWriteLock.writeLock().unlock();
  }
}