一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - SpringMVC 九大組件之 HandlerMapping 深入分析

SpringMVC 九大組件之 HandlerMapping 深入分析

2021-03-26 23:48江南一點雨 Java教程

AbstractHandlerMapping 實現(xiàn)了 HandlerMapping 接口,無論是通過 URL 進行匹配還是通過方法名進行匹配,都是通過繼承 AbstractHandlerMapping 來實現(xiàn)的,所以 AbstractHandlerMapping 所做的事情其實就是一些公共的事情,將以一些需要具體處理的事情則

 SpringMVC 九大組件之 HandlerMapping 深入分析

前面跟小伙伴們分享了 SpringMVC 一個大致的初始化流程以及請求的大致處理流程,在請求處理過程中,涉及到九大組件,分別是:

  1. HandlerMapping
  2. HandlerAdapter
  3. HandlerExceptionResolver
  4. ViewResolver
  5. RequestToViewNameTranslator
  6. LocaleResolver
  7. ThemeResolver
  8. MultipartResolver
  9. FlashMapManager

這些組件相信小伙伴們在日常開發(fā)中多多少少都有涉及到,如果你對這些組件感到陌生,可以在公眾號后臺回復 ssm,免費獲取松哥的入門視頻教程。

那么接下來的幾篇文章,松哥想和大家深入分析這九大組件,從用法到源碼,挨個分析,今天我們就先來看看這九大組件中的第一個 HandlerMapping。

1.概覽

 

HandlerMapping 叫做處理器映射器,它的作用就是根據(jù)當前 request 找到對應的 Handler 和 Interceptor,然后封裝成一個 HandlerExecutionChain 對象返回,我們來看下 HandlerMapping 接口:

  1. public interface HandlerMapping { 
  2.  String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler"
  3.  @Deprecated 
  4.  String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath"
  5.  String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping"
  6.  String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern"
  7.  String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping"
  8.  String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables"
  9.  String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables"
  10.  String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes"
  11.  default boolean usesPathPatterns() { 
  12.   return false
  13.  } 
  14.  @Nullable 
  15.  HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; 

可以看到,除了一堆聲明的常量外,其實就一個需要實現(xiàn)的方法 getHandler,該方法的返回值就是我們所了解到的 HandlerExecutionChain。

HandlerMapping 的繼承關系如下:

SpringMVC 九大組件之 HandlerMapping 深入分析

這個繼承關系雖然看著有點繞,其實仔細觀察就兩大類:

  • AbstractHandlerMethodMapping
  • AbstractUrlHandlerMapping

其他的都是一些輔助接口。

AbstractHandlerMethodMapping 體系下的都是根據(jù)方法名進行匹配的,而 AbstractUrlHandlerMapping 體系下的都是根據(jù) URL 路徑進行匹配的,這兩者有一個共同的父類 AbstractHandlerMapping,接下來我們就對這三個關鍵類進行詳細分析。

2.AbstractHandlerMapping

 

AbstractHandlerMapping 實現(xiàn)了 HandlerMapping 接口,無論是通過 URL 進行匹配還是通過方法名進行匹配,都是通過繼承 AbstractHandlerMapping 來實現(xiàn)的,所以 AbstractHandlerMapping 所做的事情其實就是一些公共的事情,將以一些需要具體處理的事情則交給子類去處理,這其實就是典型的模版方法模式。

AbstractHandlerMapping 間接繼承自 ApplicationObjectSupport,并重寫了 initApplicationContext 方法(其實該方法也是一個模版方法),這也是 AbstractHandlerMapping 的初始化入口方法,我們一起來看下:

  1. @Override 
  2. protected void initApplicationContext() throws BeansException { 
  3.  extendInterceptors(this.interceptors); 
  4.  detectMappedInterceptors(this.adaptedInterceptors); 
  5.  initInterceptors(); 

三個方法都和攔截器有關。

extendInterceptors

  1. protected void extendInterceptors(List<Object> interceptors) { 

extendInterceptors 是一個模版方法,可以在子類中實現(xiàn),子類實現(xiàn)了該方法之后,可以對攔截器進行添加、刪除或者修改,不過在 SpringMVC 的具體實現(xiàn)中,其實這個方法并沒有在子類中進行實現(xiàn)。

detectMappedInterceptors

  1. protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { 
  2.  mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors( 
  3.    obtainApplicationContext(), MappedInterceptor.class, truefalse).values()); 

detectMappedInterceptors 方法會從 SpringMVC 容器以及 Spring 容器中查找所有 MappedInterceptor 類型的 Bean,查找到之后添加到 mappedInterceptors 屬性中(其實就是全局的 adaptedInterceptors 屬性)。一般來說,我們定義好一個攔截器之后,還要在 XML 文件中配置該攔截器,攔截器以及各種配置信息,最終就會被封裝成一個 MappedInterceptor 對象。

initInterceptors

  1. protected void initInterceptors() { 
  2.  if (!this.interceptors.isEmpty()) { 
  3.   for (int i = 0; i < this.interceptors.size(); i++) { 
  4.    Object interceptor = this.interceptors.get(i); 
  5.    if (interceptor == null) { 
  6.     throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null"); 
  7.    } 
  8.    this.adaptedInterceptors.add(adaptInterceptor(interceptor)); 
  9.   } 
  10.  } 

initInterceptors 方法主要是進行攔截器的初始化操作,具體內(nèi)容是將 interceptors 集合中的攔截器添加到 adaptedInterceptors 集合中。

至此,我們看到,所有攔截器最終都會被存入 adaptedInterceptors 變量中。

AbstractHandlerMapping 的初始化其實也就是攔截器的初始化過程。

為什么 AbstractHandlerMapping 中對攔截器如此重視呢?其實不是重視,大家想想,AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping 最大的區(qū)別在于查找處理器的區(qū)別,一旦處理器找到了,再去找攔截器,但是攔截器都是統(tǒng)一的,并沒有什么明顯區(qū)別,所以攔截器就統(tǒng)一在 AbstractHandlerMapping 中進行處理,而不會去 AbstractUrlHandlerMapping 或者 AbstractHandlerMethodMapping 中處理。

接下來我們再來看看 AbstractHandlerMapping#getHandler 方法,看看處理器是如何獲取到的:

  1. @Override 
  2. @Nullable 
  3. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 
  4.  Object handler = getHandlerInternal(request); 
  5.  if (handler == null) { 
  6.   handler = getDefaultHandler(); 
  7.  } 
  8.  if (handler == null) { 
  9.   return null
  10.  } 
  11.  // Bean name or resolved handler? 
  12.  if (handler instanceof String) { 
  13.   String handlerName = (String) handler; 
  14.   handler = obtainApplicationContext().getBean(handlerName); 
  15.  } 
  16.  // Ensure presence of cached lookupPath for interceptors and others 
  17.  if (!ServletRequestPathUtils.hasCachedPath(request)) { 
  18.   initLookupPath(request); 
  19.  } 
  20.  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); 
  21.  if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { 
  22.   CorsConfiguration config = getCorsConfiguration(handler, request); 
  23.   if (getCorsConfigurationSource() != null) { 
  24.    CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); 
  25.    config = (globalConfig != null ? globalConfig.combine(config) : config); 
  26.   } 
  27.   if (config != null) { 
  28.    config.validateAllowCredentials(); 
  29.   } 
  30.   executionChain = getCorsHandlerExecutionChain(request, executionChain, config); 
  31.  } 
  32.  return executionChain; 

這個方法的執(zhí)行流程是這樣的:

  1. 首先調(diào)用 getHandlerInternal 方法去嘗試獲取處理器,getHandlerInternal 方法也是一個模版方法,該方法將在子類中實現(xiàn)。
  2. 如果沒找到相應的處理器,則調(diào)用 getDefaultHandler 方法獲取默認的處理器,我們在配置 HandlerMapping 的時候可以配置默認的處理器。
  3. 如果找到的處理器是一個字符串,則根據(jù)該字符串找去 SpringMVC 容器中找到對應的 Bean。
  4. 確保 lookupPath 存在,一會找對應的攔截器的時候會用到。
  5. 找到 handler 之后,接下來再調(diào)用 getHandlerExecutionChain 方法獲取 HandlerExecutionChain 對象。
  6. 接下來 if 里邊的是進行跨域處理的,獲取到跨域的相關配置,然后進行驗證&配置,檢查是否允許跨域??缬蜻@塊的配置以及校驗還是蠻有意思的,松哥以后專門寫文章來和小伙伴們細聊。

接下來我們再來看看第五步的 getHandlerExecutionChain 方法的執(zhí)行邏輯,正是在這個方法里邊把 handler 變成了 HandlerExecutionChain:

  1. protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { 
  2.  HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? 
  3.    (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); 
  4.  for (HandlerInterceptor interceptor : this.adaptedInterceptors) { 
  5.   if (interceptor instanceof MappedInterceptor) { 
  6.    MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; 
  7.    if (mappedInterceptor.matches(request)) { 
  8.     chain.addInterceptor(mappedInterceptor.getInterceptor()); 
  9.    } 
  10.   } 
  11.   else { 
  12.    chain.addInterceptor(interceptor); 
  13.   } 
  14.  } 
  15.  return chain; 

這里直接根據(jù)已有的 handler 創(chuàng)建一個新的 HandlerExecutionChain 對象,然后遍歷 adaptedInterceptors 集合,該集合里存放的都是攔截器,如果攔截器的類型是 MappedInterceptor,則調(diào)用 matches 方法去匹配一下,看一下是否是攔截當前請求的攔截器,如果是,則調(diào)用 chain.addInterceptor 方法加入到 HandlerExecutionChain 對象中;如果就是一個普通攔截器,則直接加入到 HandlerExecutionChain 對象中。

這就是 AbstractHandlerMapping#getHandler 方法的大致邏輯,可以看到,這里留了一個模版方法 getHandlerInternal 在子類中實現(xiàn),接下來我們就來看看它的子類。

3.AbstractUrlHandlerMapping

 

AbstractUrlHandlerMapping,看名字就知道,都是按照 URL 地址來進行匹配的,它的原理就是將 URL 地址與對應的 Handler 保存在同一個 Map 中,當調(diào)用 getHandlerInternal 方法時,就根據(jù)請求的 URL 去 Map 中找到對應的 Handler 返回就行了。

這里我們就先從他的 getHandlerInternal 方法開始看起:

  1. @Override 
  2. @Nullable 
  3. protected Object getHandlerInternal(HttpServletRequest request) throws Exception { 
  4.  String lookupPath = initLookupPath(request); 
  5.  Object handler; 
  6.  if (usesPathPatterns()) { 
  7.   RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); 
  8.   handler = lookupHandler(path, lookupPath, request); 
  9.  } 
  10.  else { 
  11.   handler = lookupHandler(lookupPath, request); 
  12.  } 
  13.  if (handler == null) { 
  14.   // We need to care for the default handler directly, since we need to 
  15.   // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. 
  16.   Object rawHandler = null
  17.   if (StringUtils.matchesCharacter(lookupPath, '/')) { 
  18.    rawHandler = getRootHandler(); 
  19.   } 
  20.   if (rawHandler == null) { 
  21.    rawHandler = getDefaultHandler(); 
  22.   } 
  23.   if (rawHandler != null) { 
  24.    // Bean name or resolved handler? 
  25.    if (rawHandler instanceof String) { 
  26.     String handlerName = (String) rawHandler; 
  27.     rawHandler = obtainApplicationContext().getBean(handlerName); 
  28.    } 
  29.    validateHandler(rawHandler, request); 
  30.    handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); 
  31.   } 
  32.  } 
  33.  return handler; 
  1. 首先找到 lookupPath,就是請求的路徑。這個方法本身松哥就不多說了,之前在Spring5 里邊的新玩法!這種 URL 請求讓我漲見識了!一文中有過介紹。
  2. 接下來就是調(diào)用 lookupHandler 方法獲取 Handler 對象,lookupHandler 有一個重載方法,具體用哪個,主要看所使用的 URL 匹配模式,如果使用了最新的 PathPattern(Spring5 之后的),則使用三個參數(shù)的 lookupHandler;如果還是使用之前舊的 AntPathMatcher,則這里使用兩個參數(shù)的 lookupHandler。
  3. 如果前面沒有獲取到 handler 實例,則接下來再做各種嘗試,去分別查找 RootHandler、DefaultHandler 等,如果找到的 Handler 是一個 String,則去 Spring 容器中查找該 String 對應的 Bean,再調(diào)用 validateHandler 方法來校驗找到的 handler 和 request 是否匹配,不過這是一個空方法,子類也沒有實現(xiàn),所以可以忽略之。最后再通過 buildPathExposingHandler 方法給找到的 handler 添加一些參數(shù)。

這就是整個 getHandlerInternal 方法的邏輯,實際上并不難,里邊主要涉及到 lookupHandler 和 buildPathExposingHandler 兩個方法,需要和大家詳細介紹下,我們分別來看。

lookupHandler

lookupHandler 有兩個,我們分別來看。

  1. @Nullable 
  2. protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception { 
  3.  Object handler = getDirectMatch(lookupPath, request); 
  4.  if (handler != null) { 
  5.   return handler; 
  6.  } 
  7.  // Pattern match? 
  8.  List<String> matchingPatterns = new ArrayList<>(); 
  9.  for (String registeredPattern : this.handlerMap.keySet()) { 
  10.   if (getPathMatcher().match(registeredPattern, lookupPath)) { 
  11.    matchingPatterns.add(registeredPattern); 
  12.   } 
  13.   else if (useTrailingSlashMatch()) { 
  14.    if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) { 
  15.     matchingPatterns.add(registeredPattern + "/"); 
  16.    } 
  17.   } 
  18.  } 
  19.  String bestMatch = null
  20.  Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath); 
  21.  if (!matchingPatterns.isEmpty()) { 
  22.   matchingPatterns.sort(patternComparator); 
  23.   bestMatch = matchingPatterns.get(0); 
  24.  } 
  25.  if (bestMatch != null) { 
  26.   handler = this.handlerMap.get(bestMatch); 
  27.   if (handler == null) { 
  28.    if (bestMatch.endsWith("/")) { 
  29.     handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); 
  30.    } 
  31.    if (handler == null) { 
  32.     throw new IllegalStateException( 
  33.       "Could not find handler for best pattern match [" + bestMatch + "]"); 
  34.    } 
  35.   } 
  36.   // Bean name or resolved handler? 
  37.   if (handler instanceof String) { 
  38.    String handlerName = (String) handler; 
  39.    handler = obtainApplicationContext().getBean(handlerName); 
  40.   } 
  41.   validateHandler(handler, request); 
  42.   String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath); 
  43.   // There might be multiple 'best patterns', let's make sure we have the correct URI template variables 
  44.   // for all of them 
  45.   Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); 
  46.   for (String matchingPattern : matchingPatterns) { 
  47.    if (patternComparator.compare(bestMatch, matchingPattern) == 0) { 
  48.     Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); 
  49.     Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); 
  50.     uriTemplateVariables.putAll(decodedVars); 
  51.    } 
  52.   } 
  53.   return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); 
  54.  } 
  55.  // No handler found... 
  56.  return null
  57. @Nullable 
  58. private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception { 
  59.  Object handler = this.handlerMap.get(urlPath); 
  60.  if (handler != null) { 
  61.   // Bean name or resolved handler? 
  62.   if (handler instanceof String) { 
  63.    String handlerName = (String) handler; 
  64.    handler = obtainApplicationContext().getBean(handlerName); 
  65.   } 
  66.   validateHandler(handler, request); 
  67.   return buildPathExposingHandler(handler, urlPath, urlPath, null); 
  68.  } 
  69.  return null

1.這里首先調(diào)用 getDirectMatch 方法直接去 handlerMap 中找對應的處理器,handlerMap 中就保存了請求 URL 和處理器的映射關系,具體的查找過程就是先去 handlerMap 中找,找到了,如果是 String,則去 Spring 容器中找對應的 Bean,然后調(diào)用 validateHandler 方法去驗證(實際上沒有驗證,前面已經(jīng)說了),最后調(diào)用 buildPathExposingHandler 方法添加攔截器。

2.如果 getDirectMatch 方法返回值不為 null,則直接將查找到的 handler 返回,方法到此為止。那么什么情況下 getDirectMatch 方法的返回值不為 null 呢?簡單來收就是沒有使用通配符的情況下,請求地址中沒有通配符,一個請求地址對應一個處理器,只有這種情況,getDirectMatch 方法返回值才不為 null,因為 handlerMap 中保存的是代碼的定義,比如我們定義代碼的時候,某個處理器的訪問路徑可能帶有通配符,但是當我們真正發(fā)起請求的時候,請求路徑里是沒有通配符的,這個時候再去 handlerMap 中就找不對對應的處理器了。如果用到了定義接口時用到了通配符,則需要在下面的代碼中繼續(xù)處理。

3.接下來處理通配符的情況。首先定義 matchingPatterns 集合,將當前請求路徑和 handlerMap 集合中保存的請求路徑規(guī)則進行對比,凡是能匹配上的規(guī)則都直接存入 matchingPatterns 集合中。具體處理中,還有一個 useTrailingSlashMatch 的可能,有的小伙伴 SpringMVC 用的不熟練,看到這里可能就懵了,這里是這樣的,SpringMVC 中,默認是可以匹配結(jié)尾 / 的,舉個簡單例子,如果你定義的接口是/user,那么請求路徑可以是 /user 也可以 /user/,這兩種默認都是支持的,所以這里的 useTrailingSlashMatch 分支主要是處理后面這種情況,處理方式很簡單,就在 registeredPattern 后面加上 / 然后繼續(xù)和請求路徑進行匹配。

4.由于一個請求 URL 可能會和定義的多個接口匹配上,所以 matchingPatterns 變量是一個數(shù)組,接下來就要對 matchingPatterns 進行排序,排序完成后,選擇排序后的第一項作為最佳選項賦值給 bestMatch 變量。默認的排序規(guī)則是 AntPatternComparator,當然開發(fā)者也可以自定義。AntPatternComparator 中定義的優(yōu)先級如下:

路由配置 優(yōu)先級
不含任何特殊符號的路徑,如:配置路由/a/b/c 第一優(yōu)先級
帶有{}的路徑,如:/a//c 第二優(yōu)先級
帶有正則的路徑,如:/a/{regex:\d{3}}/c 第三優(yōu)先級
帶有*的路徑,如:/a/b/* 第四優(yōu)先級
帶有**的路徑,如:/a/b/** 第五優(yōu)先級
最模糊的匹配:/** 最低優(yōu)先級

5.找到 bestMatch 之后,接下來再根據(jù) bestMatch 去 handlerMap 中找到對應的處理器,直接找如果沒找到,就去檢查 bestMatch 是否以 / 結(jié)尾,如果是以 / 結(jié)尾,則去掉結(jié)尾的 / 再去 handlerMap 中查找,如果還沒找到,那就該拋異常出來了。如果找到的 handler 是 String 類型的,則再去 Spring 容器中查找對應的 Bean,接下來再調(diào)用 validateHandler 方法進行驗證。

6.接下來調(diào)用 extractPathWithinPattern 方法提取出映射路徑,例如定義的接口規(guī)則是 myroot/*.html,請求路徑是 myroot/myfile.html,那么最終獲取到的就是myfile.html。

7.接下來的 for 循環(huán)是為了處理存在多個最佳匹配規(guī)則的情況,在第四步中,我們對 matchingPatterns 進行排序,排序完成后,選擇第一項作為最佳選項賦值給 bestMatch,但是最佳選項可能會有多個,這里就是處理最佳選項有多個的情況。

8.最后調(diào)用 buildPathExposingHandler 方法注冊兩個內(nèi)部攔截器,該方法下文我會給大家詳細介紹。

lookupHandler 還有一個重載方法,不過只要大家把這個方法的執(zhí)行流程搞清楚了,重載方法其實很好理解,這里松哥就不再贅述了,唯一要說的就是重載方法用了 PathPattern 去匹配 URL 路徑,而這個方法用了 AntPathMatcher 去匹配 URL 路徑。

buildPathExposingHandler

  1. protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, 
  2.   String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) { 
  3.  HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler); 
  4.  chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping)); 
  5.  if (!CollectionUtils.isEmpty(uriTemplateVariables)) { 
  6.   chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); 
  7.  } 
  8.  return chain; 

buildPathExposingHandler 方法向 HandlerExecutionChain 中添加了兩個攔截器 PathExposingHandlerInterceptor 和 UriTemplateVariablesHandlerInterceptor,這兩個攔截器在各自的 preHandle 中分別向 request 對象添加了一些屬性,具體添加的屬性小伙伴們可以自行查看,這個比較簡單,我就不多說了。

在前面的方法中,涉及到一個重要的變量 handlerMap,我們定義的接口和處理器之間的關系都保存在這個變量中,那么這個變量是怎么初始化的呢?這就涉及到 AbstractUrlHandlerMapping 中的另一個方法 registerHandler:

  1. protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { 
  2.  for (String urlPath : urlPaths) { 
  3.   registerHandler(urlPath, beanName); 
  4.  } 
  5. protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { 
  6.  Object resolvedHandler = handler; 
  7.  if (!this.lazyInitHandlers && handler instanceof String) { 
  8.   String handlerName = (String) handler; 
  9.   ApplicationContext applicationContext = obtainApplicationContext(); 
  10.   if (applicationContext.isSingleton(handlerName)) { 
  11.    resolvedHandler = applicationContext.getBean(handlerName); 
  12.   } 
  13.  } 
  14.  Object mappedHandler = this.handlerMap.get(urlPath); 
  15.  if (mappedHandler != null) { 
  16.   if (mappedHandler != resolvedHandler) { 
  17.    throw new IllegalStateException( 
  18.      "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + 
  19.      "]: There is already " + getHandlerDescription(mappedHandler) + " mapped."); 
  20.   } 
  21.  } 
  22.  else { 
  23.   if (urlPath.equals("/")) { 
  24.    setRootHandler(resolvedHandler); 
  25.   } 
  26.   else if (urlPath.equals("/*")) { 
  27.    setDefaultHandler(resolvedHandler); 
  28.   } 
  29.   else { 
  30.    this.handlerMap.put(urlPath, resolvedHandler); 
  31.    if (getPatternParser() != null) { 
  32.     this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); 
  33.    } 
  34.   } 
  35.  } 

registerHandler(String[],String) 方法有兩個參數(shù),第一個就是定義的請求路徑,第二個參數(shù)則是處理器 Bean 的名字,第一個參數(shù)是一個數(shù)組,那是因為同一個處理器可以對應多個不同的請求路徑。

在重載方法 registerHandler(String,String) 里邊,完成了 handlerMap 的初始化,具體流程如下:

  1. 如果沒有設置 lazyInitHandlers,并且 handler 是 String 類型,那么就去 Spring 容器中找到對應的 Bean 賦值給 resolvedHandler。
  2. 根據(jù) urlPath 去 handlerMap 中查看是否已經(jīng)有對應的處理器了,如果有的話,則拋出異常,一個 URL 地址只能對應一個處理器,這個很好理解。
  3. 接下來根據(jù) URL 路徑,將處理器進行配置,最終添加到 handlerMap 變量中。

這就是 AbstractUrlHandlerMapping 的主要工作,其中 registerHandler 將在它的子類中調(diào)用。

接下來我們來看 AbstractUrlHandlerMapping 的子類。

3.1 SimpleUrlHandlerMapping

為了方便處理,SimpleUrlHandlerMapping 中自己定義了一個 urlMap 變量,這樣可以在注冊之前做一些預處理,例如確保所有的 URL 都是以 / 開始。SimpleUrlHandlerMapping 在定義時重寫了父類的 initApplicationContext 方法,并在該方法中調(diào)用了 registerHandlers,在 registerHandlers 中又調(diào)用了父類的 registerHandler 方法完成了 handlerMap 的初始化操作:

  1. @Override 
  2. public void initApplicationContext() throws BeansException { 
  3.  super.initApplicationContext(); 
  4.  registerHandlers(this.urlMap); 
  5. protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { 
  6.  if (urlMap.isEmpty()) { 
  7.   logger.trace("No patterns in " + formatMappingName()); 
  8.  } 
  9.  else { 
  10.   urlMap.forEach((url, handler) -> { 
  11.    // Prepend with slash if not already present. 
  12.    if (!url.startsWith("/")) { 
  13.     url = "/" + url; 
  14.    } 
  15.    // Remove whitespace from handler bean name
  16.    if (handler instanceof String) { 
  17.     handler = ((String) handler).trim(); 
  18.    } 
  19.    registerHandler(url, handler); 
  20.   }); 
  21.  } 

這塊代碼很簡單,實在沒啥好說的,如果 URL 不是以 / 開頭,則手動給它加上/ 即可。有小伙伴們可能要問了,urlMap 的值從哪里來?當然是從我們的配置文件里邊來呀,像下面這樣:

  1. <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
  2.     <property name="urlMap"
  3.         <map> 
  4.             <entry key="/aaa" value-ref="/hello"/> 
  5.         </map> 
  6.     </property> 
  7. </bean> 

3.2 AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping 也是 AbstractUrlHandlerMapping 的子類,但是它和 SimpleUrlHandlerMapping 有一些不一樣的地方。

不一樣的是哪里呢?

AbstractDetectingUrlHandlerMapping 會自動查找到 SpringMVC 容器以及 Spring 容器中的所有 beanName,然后根據(jù) beanName 解析出對應的 URL 地址,再將解析出的 url 地址和對應的 beanName 注冊到父類的 handlerMap 變量中。換句話說,如果你用了 AbstractDetectingUrlHandlerMapping,就不用像 SimpleUrlHandlerMapping 那樣去挨個配置 URL 地址和處理器的映射關系了。我們來看下 AbstractDetectingUrlHandlerMapping#initApplicationContext 方法:

  1. @Override 
  2. public void initApplicationContext() throws ApplicationContextException { 
  3.  super.initApplicationContext(); 
  4.  detectHandlers(); 
  5. protected void detectHandlers() throws BeansException { 
  6.  ApplicationContext applicationContext = obtainApplicationContext(); 
  7.  String[] beanNames = (this.detectHandlersInAncestorContexts ? 
  8.    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : 
  9.    applicationContext.getBeanNamesForType(Object.class)); 
  10.  for (String beanName : beanNames) { 
  11.   String[] urls = determineUrlsForHandler(beanName); 
  12.   if (!ObjectUtils.isEmpty(urls)) { 
  13.    registerHandler(urls, beanName); 
  14.   } 
  15.  } 

AbstractDetectingUrlHandlerMapping 重寫了父類的 initApplicationContext 方法,并在該方法中調(diào)用了 detectHandlers 方法,在 detectHandlers 中,首先查找到所有的 beanName,然后調(diào)用 determineUrlsForHandler 方法分析出 beanName 對應的 URL,不過這里的 determineUrlsForHandler 方法是一個空方法,具體的實現(xiàn)在它的子類中,AbstractDetectingUrlHandlerMapping 只有一個子類 BeanNameUrlHandlerMapping,我們一起來看下:

  1. public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { 
  2.  @Override 
  3.  protected String[] determineUrlsForHandler(String beanName) { 
  4.   List<String> urls = new ArrayList<>(); 
  5.   if (beanName.startsWith("/")) { 
  6.    urls.add(beanName); 
  7.   } 
  8.   String[] aliases = obtainApplicationContext().getAliases(beanName); 
  9.   for (String alias : aliases) { 
  10.    if (alias.startsWith("/")) { 
  11.     urls.add(alias); 
  12.    } 
  13.   } 
  14.   return StringUtils.toStringArray(urls); 
  15.  } 
  16.  

這個類很簡單,里邊就一個 determineUrlsForHandler 方法,這個方法的執(zhí)行邏輯也很簡單,就判斷 beanName 是不是以 / 開始,如果是,則將之作為 URL。

如果我們想要在項目中使用 BeanNameUrlHandlerMapping,配置方式如下:

  1. <bean class="org.javaboy.init.HelloController" name="/hello"/> 
  2. <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping"
  3. </bean> 

注意,Controller 的 name 必須是以 / 開始,否則該 bean 不會被自動作為處理器。

至此,AbstractUrlHandlerMapping 體系下的東西就和大家分享完了。

4.AbstractHandlerMethodMapping

 

AbstractHandlerMethodMapping 體系下只有三個類,分別是 AbstractHandlerMethodMapping、RequestMappingInfoHandlerMapping 以及 RequestMappingHandlerMapping,如下圖:

SpringMVC 九大組件之 HandlerMapping 深入分析

在前面第三小節(jié)的 AbstractUrlHandlerMapping 體系下,一個 Handler 一般就是一個類,但是在 AbstractHandlerMethodMapping 體系下,一個 Handler 就是一個 Mehtod,這也是我們目前使用 SpringMVC 時最常見的用法,即直接用 @RequestMapping 去標記一個方法,該方法就是一個 Handler。

接下來我們就一起來看看 AbstractHandlerMethodMapping。

4.1 初始化流程

AbstractHandlerMethodMapping 類實現(xiàn)了 InitializingBean 接口,所以 Spring 容器會自動調(diào)用其 afterPropertiesSet 方法,在這里將完成初始化操作:

  1. @Override 
  2. public void afterPropertiesSet() { 
  3.  initHandlerMethods(); 
  4. protected void initHandlerMethods() { 
  5.  for (String beanName : getCandidateBeanNames()) { 
  6.   if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { 
  7.    processCandidateBean(beanName); 
  8.   } 
  9.  } 
  10.  handlerMethodsInitialized(getHandlerMethods()); 
  11. protected String[] getCandidateBeanNames() { 
  12.  return (this.detectHandlerMethodsInAncestorContexts ? 
  13.    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : 
  14.    obtainApplicationContext().getBeanNamesForType(Object.class)); 
  15. protected void processCandidateBean(String beanName) { 
  16.  Class<?> beanType = null
  17.  try { 
  18.   beanType = obtainApplicationContext().getType(beanName); 
  19.  } 
  20.  catch (Throwable ex) { 
  21.  } 
  22.  if (beanType != null && isHandler(beanType)) { 
  23.   detectHandlerMethods(beanName); 
  24.  } 

可以看到,具體的初始化又是在 initHandlerMethods 方法中完成的,在該方法中,首先調(diào)用 getCandidateBeanNames 方法獲取容器中所有的 beanName,然后調(diào)用 processCandidateBean 方法對這些候選的 beanName 進行處理,具體的處理思路就是根據(jù) beanName 找到 beanType,然后調(diào)用 isHandler 方法判斷該 beanType 是不是一個 Handler,isHandler 是一個空方法,在它的子類 RequestMappingHandlerMapping 中被實現(xiàn)了,該方法主要是檢查該 beanType 上有沒有 @Controller 或者 @RequestMapping 注解,如果有,說明這就是我們想要的 handler,接下來再調(diào)用 detectHandlerMethods 方法保存 URL 和 handler 的映射關系:

  1. protected void detectHandlerMethods(Object handler) { 
  2.  Class<?> handlerType = (handler instanceof String ? 
  3.    obtainApplicationContext().getType((String) handler) : handler.getClass()); 
  4.  if (handlerType != null) { 
  5.   Class<?> userType = ClassUtils.getUserClass(handlerType); 
  6.   Map<Method, T> methods = MethodIntrospector.selectMethods(userType, 
  7.     (MethodIntrospector.MetadataLookup<T>) method -> { 
  8.      try { 
  9.       return getMappingForMethod(method, userType); 
  10.      } 
  11.      catch (Throwable ex) { 
  12.       throw new IllegalStateException("Invalid mapping on handler class [" + 
  13.         userType.getName() + "]: " + method, ex); 
  14.      } 
  15.     }); 
  16.   methods.forEach((method, mapping) -> { 
  17.    Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); 
  18.    registerHandlerMethod(handler, invocableMethod, mapping); 
  19.   }); 
  20.  } 
  1. 首先找到 handler 的類型 handlerType。
  2. 調(diào)用 ClassUtils.getUserClass 方法檢查是否是 cglib 代理的子對象類型,如果是,則返回父類型,否則將參數(shù)直接返回。
  3. 接下來調(diào)用 MethodIntrospector.selectMethods 方法獲取當前 bean 中所有符合要求的 method。
  4. 遍歷 methods,調(diào)用 registerHandlerMethod 方法完成注冊。

上面這段代碼里又涉及到兩個方法:

  • getMappingForMethod
  • registerHandlerMethod

我們分別來看:

getMappingForMethod

getMappingForMethod 是一個模版方法,具體的實現(xiàn)也是在子類 RequestMappingHandlerMapping 里邊:

  1. @Override 
  2. @Nullable 
  3. protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { 
  4.  RequestMappingInfo info = createRequestMappingInfo(method); 
  5.  if (info != null) { 
  6.   RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); 
  7.   if (typeInfo != null) { 
  8.    info = typeInfo.combine(info); 
  9.   } 
  10.   String prefix = getPathPrefix(handlerType); 
  11.   if (prefix != null) { 
  12.    info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); 
  13.   } 
  14.  } 
  15.  return info; 

首先根據(jù) method 對象,調(diào)用 createRequestMappingInfo 方法獲取一個 RequestMappingInfo,一個 RequestMappingInfo 包含了一個接口定義的詳細信息,例如參數(shù)、header、produces、consumes、請求方法等等信息都在這里邊。接下來再根據(jù) handlerType 也獲取一個 RequestMappingInfo,并調(diào)用 combine 方法將兩個 RequestMappingInfo 進行合并。接下來調(diào)用 getPathPrefix 方法查看 handlerType 上有沒有 URL 前綴,如果有,就添加到 info 里邊去,最后將 info 返回。

這里要說一下 handlerType 里邊的這個前綴是那里來的,我們可以在 Controller 上使用 @RequestMapping 注解,配置一個路徑前綴,這樣 Controller 中的所有方法都加上了該路徑前綴,但是這種方式需要一個一個的配置,如果想一次性配置所有的 Controller 呢?我們可以使用 Spring5.1 中新引入的方法 addPathPrefix 來配置,如下:

  1. @Configuration 
  2. public class WebConfig implements WebMvcConfigurer { 
  3.  
  4.     @Override 
  5.     public void configurePathMatch(PathMatchConfigurer configurer) { 
  6.         configurer.setPatternParser(new PathPatternParser()).addPathPrefix("/itboyhub", HandlerTypePredicate.forAnnotation(RestController.class)); 
  7.     } 

上面這個配置表示,所有的 @RestController 標記的類都自動加上 itboyhub前綴。有了這個配置之后,上面的 getPathPrefix 方法獲取到的就是/itboyhub 了。

registerHandlerMethod

當找齊了 URL 和 handlerMethod 之后,接下來就是將這些信息保存下來,方式如下:

  1. protected void registerHandlerMethod(Object handler, Method method, T mapping) { 
  2.  this.mappingRegistry.register(mapping, handler, method); 
  3. public void register(T mapping, Object handler, Method method) { 
  4.  this.readWriteLock.writeLock().lock(); 
  5.  try { 
  6.   HandlerMethod handlerMethod = createHandlerMethod(handler, method); 
  7.   validateMethodMapping(handlerMethod, mapping); 
  8.   Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); 
  9.   for (String path : directPaths) { 
  10.    this.pathLookup.add(path, mapping); 
  11.   } 
  12.   String name = null
  13.   if (getNamingStrategy() != null) { 
  14.    name = getNamingStrategy().getName(handlerMethod, mapping); 
  15.    addMappingName(name, handlerMethod); 
  16.   } 
  17.   CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); 
  18.   if (corsConfig != null) { 
  19.    corsConfig.validateAllowCredentials(); 
  20.    this.corsLookup.put(handlerMethod, corsConfig); 
  21.   } 
  22.   this.registry.put(mapping, 
  23.     new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null)); 
  24.  } 
  25.  finally { 
  26.   this.readWriteLock.writeLock().unlock(); 
  27.  } 
  1. 首先調(diào)用 createHandlerMethod 方法創(chuàng)建 HandlerMethod 對象。
  2. 調(diào)用 validateMethodMapping 方法對 handlerMethod 進行驗證,主要是驗證 handlerMethod 是否已經(jīng)存在。
  3. 從 mappings 中提取出 directPaths,就是不包含通配符的請求路徑,然后將請求路徑和 mapping 的映射關系保存到 pathLookup 中。
  4. 找到所有 handler 的簡稱,調(diào)用 addMappingName 方法添加到 nameLookup 中。例如我們在 HelloController 中定義了一個名為 hello 的請求接口,那么這里拿到的就是 HC#hello,HC 是 HelloController 中的大寫字母。
  5. 初始化跨域配置,并添加到 corsLookup 中。
  6. 將構(gòu)建好的關系添加到 registry 中。

多說一句,第四步這個東西有啥用呢?這個其實是 Spring4 中開始增加的功能,算是一個小彩蛋吧,雖然日常開發(fā)很少用,但是我這里還是和大家說一下。

假如你有如下一個接口:

  1. @RestController 
  2. @RequestMapping("/javaboy"
  3. public class HelloController { 
  4.     @GetMapping("/aaa"
  5.     public String hello99() { 
  6.         return "aaa"
  7.     } 

當你請求該接口的時候,不想通過路徑,想直接通過方法名,行不行呢?當然可以!

在 jsp 文件中,添加如下超鏈接:

  1. <%@ taglib prefix="s" uri="http://www.springframework.org/tags" %> 
  2. <%@ page contentType="text/html;charset=UTF-8" language="java" %> 
  3. <html> 
  4. <head> 
  5.     <title>Title</title> 
  6. </head> 
  7. <body> 
  8. <a href="${s:mvcUrl('HC#hello99').build()}">Go!</a> 
  9. </body> 
  10. </html> 

當這個 jsp 頁面渲染完成后,href 屬性就自動成了 hello99 方法的請求路徑了。這個功能的實現(xiàn),就依賴于前面第四步的內(nèi)容。

至此,我們就把 AbstractHandlerMethodMapping 的初始化流程看完了。

4.2 請求處理

接下來我們來看下當請求到來后,AbstractHandlerMethodMapping 會如何處理。

和前面第三小節(jié)一樣,這里處理請求的入口方法也是 getHandlerInternal,如下:

  1. @Override 
  2. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { 
  3.  String lookupPath = initLookupPath(request); 
  4.  this.mappingRegistry.acquireReadLock(); 
  5.  try { 
  6.   HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); 
  7.   return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); 
  8.  } 
  9.  finally { 
  10.   this.mappingRegistry.releaseReadLock(); 
  11.  } 
  12. protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { 
  13.  List<Match> matches = new ArrayList<>(); 
  14.  List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); 
  15.  if (directPathMatches != null) { 
  16.   addMatchingMappings(directPathMatches, matches, request); 
  17.  } 
  18.  if (matches.isEmpty()) { 
  19.   addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); 
  20.  } 
  21.  if (!matches.isEmpty()) { 
  22.   Match bestMatch = matches.get(0); 
  23.   if (matches.size() > 1) { 
  24.    Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); 
  25.    matches.sort(comparator); 
  26.    bestMatch = matches.get(0); 
  27.    if (CorsUtils.isPreFlightRequest(request)) { 
  28.     for (Match match : matches) { 
  29.      if (match.hasCorsConfig()) { 
  30.       return PREFLIGHT_AMBIGUOUS_MATCH; 
  31.      } 
  32.     } 
  33.    } 
  34.    else { 
  35.     Match secondBestMatch = matches.get(1); 
  36.     if (comparator.compare(bestMatch, secondBestMatch) == 0) { 
  37.      Method m1 = bestMatch.getHandlerMethod().getMethod(); 
  38.      Method m2 = secondBestMatch.getHandlerMethod().getMethod(); 
  39.      String uri = request.getRequestURI(); 
  40.      throw new IllegalStateException( 
  41.        "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); 
  42.     } 
  43.    } 
  44.   } 
  45.   request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); 
  46.   handleMatch(bestMatch.mapping, lookupPath, request); 
  47.   return bestMatch.getHandlerMethod(); 
  48.  } 
  49.  else { 
  50.   return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); 
  51.  } 

這里就比較容易,通過 lookupHandlerMethod 找到對應的 HandlerMethod 返回即可,如果 lookupHandlerMethod 方法返回值不為 null,則通過 createWithResolvedBean 創(chuàng)建 HandlerMethod(主要是確認里邊的 Bean 等),具體的創(chuàng)建過程松哥在后面的文章中會專門和大家分享。lookupHandlerMethod 方法也比較容易:

  1. 首先根據(jù) lookupPath 找到匹配條件 directPathMatches,然后將獲取到的匹配條件添加到 matches 中(不包含通配符的請求走這里)。
  2. 如果 matches 為空,說明根據(jù) lookupPath 沒有找到匹配條件,那么直接將所有匹配條件加入 matches 中(包含通配符的請求走這里)。
  3. 對 matches 進行排序,并選擇排序后的第一個為最佳匹配項,如果前兩個排序相同,則拋出異常。
  4. 大致的流程就是這樣,具體到請求并沒有涉及到它的子類。

5.小結(jié)

SpringMVC 九大組件,今天和小伙伴們把 HandlerMapping 過了一遍,其實只要認真看,這里并沒有難點。如果小伙伴們覺得閱讀吃力,也可以在公眾號后臺回復 ssm,查看松哥錄制的免費入門教程~

原文地址:https://mp.weixin.qq.com/s/0x7_OXPDFX5BqF0jGxN2Vg

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 色老妇 | 欧美腐剧mm在线观看 | 特黄特色大片免费高清视频 | 波多野结衣52部合集在线观看 | 亚洲成人国产 | 免费片在线观看高清 | 色婷婷久久综合中文久久一本 | 猫咪maomiav永久网址 | 国产精品原创巨作无遮挡 | 亚洲高清在线精品一区 | 免费欧美一级片 | 亚洲日本中文字幕天堂网 | 精品日韩一区二区三区 | 国产精品边做边接电话在线观看 | 亚洲第一色网 | 美女在线看永久免费网址 | 精品91自产拍在线观看99re | 草草视频免费在线观看 | 国产一区二区免费不卡在线播放 | 欧美日韩亚洲一区二区三区在线观看 | 波多野结衣护士 | 亚洲国产成人综合 | 四虎地址8848aa4hc44四虎 四虎成人永久地址 | 日本护士xxxx视频 | 天天做天天爽天天谢 | fuqer日本老师| 男女全黄h全肉细节文 | 亚洲人成影院午夜网站 | 极品丝袜老师h系列全文阅读 | 无人在线观看免费高清视频播放 | 性色视频免费 | 国产拍拍视频一二三四区 | 成人免费公开视频 | 国内精品久久久久影院中国 | 久久艹影院 | 日本视频免费在线观看 | 人人爽人人草 | 日本破处| 久青草国产97香蕉在线视频 | 91色porny| 母乳在线播放 |