欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring源碼之請求路徑匹配路由方式

 更新時(shí)間:2021年09月08日 11:21:52   作者:lz710117239  
這篇文章主要介紹了Spring源碼之請求路徑匹配路由方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

請求路徑匹配路由

在spring中,當(dāng)一個(gè)請求過來的時(shí)候會(huì)做路徑匹配,下面我們就從源碼層面分析一下路徑匹配。

示例:

@RequestMapping(value = "/user/{aid}/online/**", method = RequestMethod.GET)

我們一起看看這個(gè)方法是如何尋找的,和一些相應(yīng)的工具類

入口

我的項(xiàng)目使用的是自動(dòng)配置的RequestMappingHandlerMapping類,在getHandlerInternal()方法中:

HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

上面這行是根據(jù)你的請求path和request去查找合適的method了。在項(xiàng)目啟動(dòng)的時(shí)候,Spring就把路徑和對應(yīng)的方法加載到了內(nèi)存中。

進(jìn)入上面方法

  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);
  }

可以看到如果根據(jù)lookupPath直接匹配上了,走第一個(gè)方法,如果沒有,則需要根據(jù)規(guī)則匹配,走第二個(gè)方法。

mappingRegistry.getMappings().keySer()這個(gè)方法獲取的類型為RequestMappingInfo類型,后面進(jìn)入了RequestMappingInfo的getMatchingCondition()方法:

 public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
  RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
  ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
  HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
  ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
  ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
 
  if (methods == null || params == null || headers == null || consumes == null || produces == null) {
   return null;
  }
 
  PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
  if (patterns == null) {
   return null;
  }

可以看到代碼里面會(huì)查看各種條件是否匹配,包括,請求方法methods,參數(shù)params,請求頭headers,還出入?yún)㈩愋偷认嚓P(guān)的consumers,produces等,最后一行就是我們要找的路徑匹配patternsCondition.getMatchingCondition(request)。

這個(gè)方法會(huì)走到PatternRequestCondition的getMatchingPattern方法,然后調(diào)用如下方法,獲取pattern:

  if (this.pathMatcher.match(pattern, lookupPath)) {
   return pattern;
  }

上面這個(gè)pathMatcher的類型就是AntPathMatcher類,就是通過調(diào)用AntPathMatcher類的match方法,查看是否匹配,然后返回pattern。

SpringMVC 將請求找到匹配的處理

在SpringMVC的模式下,瀏覽器的一個(gè)請求是如何映射到指定的controller的呢?

初始化映射關(guān)系

在web服務(wù)器啟動(dòng)時(shí),Spring容器中會(huì)保存一個(gè)map的數(shù)據(jù)結(jié)構(gòu),里邊記錄這controller和url請求中的對應(yīng)關(guān)系。那么這個(gè)map中的數(shù)據(jù)是如何來的呢?

首先來看AbstractHandlerMethodMapping的initHandlerMethods方法(至于為什么直接找到這個(gè)方法,我也是網(wǎng)上搜索的,之前的調(diào)用鏈沒去糾結(jié))

protected void initHandlerMethods() {
 if (logger.isDebugEnabled()) {
   logger.debug("Looking for request mappings in application context: " + getApplicationContext());
  }
 
        //獲取Spring容器裝配的所有bean的名稱
 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
    getApplicationContext().getBeanNamesForType(Object.class));
 
           //遍歷
 for (String beanName : beanNames) {
                //判斷該bean是否有@controller或者@RequestMapping注解
  if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
    isHandler(getApplicationContext().getType(beanName))){
                        //如果有上述注解,則需要保存對應(yīng)關(guān)系
   detectHandlerMethods(beanName);
  }
 }
 handlerMethodsInitialized(getHandlerMethods());
}
protected void detectHandlerMethods(final Object handler) {
        //獲取傳過來handler的類信息
 Class<?> handlerType =
   (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
 
 // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
        //初始化一個(gè)保存映射信息的map
 final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
 final Class<?> userType = ClassUtils.getUserClass(handlerType);
 
 Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
  @Override
  public boolean matches(Method method) {
                //獲取該類里所有方法的映射信息 T為RequestMappingInfo
                //mapping值的形式為{[/test/test1],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
   T mapping = getMappingForMethod(method, userType);
   if (mapping != null) {
                                //將信息加入map
    mappings.put(method, mapping);
    return true;
   }
   else {
    return false;
   }
  }
 });
 
 for (Method method : methods) {
                //注冊HandlerMethod,在里邊進(jìn)行一些重復(fù)驗(yàn)證等
  registerHandlerMethod(handler, method, mappings.get(method));
 }
}

上述方法中調(diào)用了一個(gè)比較重要的方法,getMappingForMethod,通過這個(gè)方法生成后續(xù)我們一直會(huì)用到的一個(gè)RequestMappingInfo對象。具體方法如下:

@Override
//該方法接收兩個(gè)參數(shù),一個(gè)是具體方法,一個(gè)是方法所在的類
 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
  RequestMappingInfo info = null;
//找到方法的@RequestMapping注解
  RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
  if (methodAnnotation != null) {
//這個(gè)方法返回null
   RequestCondition<?> methodCondition = getCustomMethodCondition(method);
//創(chuàng)建RequestMappingInfo對象
   info = createRequestMappingInfo(methodAnnotation, methodCondition);
//找到類的@RequestMapping注解
   RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
   if (typeAnnotation != null) {
//該方法也返回一個(gè)null
    RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
//如果類和方法都有@RequestMapping注解,則進(jìn)行combine操作
    info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
   }
  }
  return info;
 }

那么上述方法中調(diào)用的createRequestMappingInfo方法有事如何真正的創(chuàng)建出一個(gè)RequestMappingInfo對象的呢?

protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
//拿到@RequestMapping注解上的value值
  String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
//創(chuàng)建一個(gè)RequestMappingInfo,參數(shù)為一堆condition,出了PatternsRequestCondition,其余全部使用@RequestMapping注解上的值
  return new RequestMappingInfo(
    annotation.name(),
    new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
      this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
    new RequestMethodsRequestCondition(annotation.method()),
    new ParamsRequestCondition(annotation.params()),
    new HeadersRequestCondition(annotation.headers()),
    new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
    new ProducesRequestCondition(annotation.produces(), annotation.headers(), this.contentNegotiationManager),
    customCondition);
 }
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//handler此處為帶有@controller或者@RequestMapping的類的名稱
//初始化一個(gè)HandlerMethod,包含一些類的名稱和方法等信息
  HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
  HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
//判斷是否有handlerMethods是否有重復(fù)數(shù)據(jù),有則拋異常,沒有則將其加入handlerMethods map中
  if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
   throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
     "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
     oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
  }
 
  this.handlerMethods.put(mapping, newHandlerMethod);
  if (logger.isInfoEnabled()) {
   logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
  }
 
//將沒有*號和問好的pattern加入到urlMap中
  Set<String> patterns = getMappingPathPatterns(mapping);
  for (String pattern : patterns) {
   if (!getPathMatcher().isPattern(pattern)) {
    this.urlMap.add(pattern, mapping);
   }
  }
 
//維護(hù)一個(gè)nameMap,key為名字,格式為congroller類大寫字母+#+方法名
//比如TestBank類的getBank方法,可以為TB#getBank
  if (this.namingStrategy != null) {
   String name = this.namingStrategy.getName(newHandlerMethod, mapping);
   updateNameMap(name, newHandlerMethod);
  }
 }

由上述registerHandlerMethod方法我們可以看出,該方法共維護(hù)了三個(gè)map分別是:

  • handlermethods: key為RequestMappingInfo value為HandlerMethod
  • urlMap: key為沒有*和?的pattern(比如/test/test1)value為RequestMappingInfo
  • nameMap: key為名字,格式為congroller類大寫字母+#+方法名,比如TestBank類的getBank方法,key為TB#getBank

上述三個(gè)map在后續(xù)匹配瀏覽器請求用哪個(gè)方法來處理時(shí)會(huì)重點(diǎn)用到。

從映射關(guān)系中尋找匹配方法

那么DispatcherServlet是如何處理一個(gè)請求的呢?

我們從DispatcherServlet的doService方法來看起,該方法中,最終會(huì)調(diào)用到AbstractHandlerMethodMapping類的lookupHandlerMethod方法來確定這個(gè)請求應(yīng)該由哪個(gè)方法處理,代碼如下:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  List<Match> matches = new ArrayList<Match>();
//從urlMap中尋找能匹配的處理方法
  List<T> directPathMatches = this.urlMap.get(lookupPath);
//如果從urlMap中找到匹配的處理方法,則調(diào)用addMatchingMappings方法,將匹配的方法放入matches集合
  if (directPathMatches != null) {
   addMatchingMappings(directPathMatches, matches, request);
  }
//如果urlMap中沒有找到直接匹配的方法
  if (matches.isEmpty()) {
   // No choice but to go through all mappings...
   addMatchingMappings(this.handlerMethods.keySet(), matches, request);
  }
 
  if (!matches.isEmpty()) {
//如果找到了匹配的方法,先獲取一個(gè)比較器
   Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
//將匹配到的方法按照比較器排序
   Collections.sort(matches, comparator);
   if (logger.isTraceEnabled()) {
    logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
   }
//如果成功匹配的方法只有一個(gè),拿這個(gè)方法返回。如果匹配到多個(gè)方法,取最匹配的前兩個(gè)進(jìn)行比較。
//如果比較結(jié)果為0,則拋出沒有找到唯一合適處理方法的異常
   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();
     throw new IllegalStateException(
       "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
       m1 + ", " + m2 + "}");
    }
   }
   handleMatch(bestMatch.mapping, lookupPath, request);
   return bestMatch.handlerMethod;
  }
  else {
//沒有找到匹配的則返回null
   return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
  }
 }

從上述代碼可以看出,程序會(huì)先從this.urlMap中尋找是否有匹配的方法,那么這個(gè)urlMap中的數(shù)據(jù)是從什么時(shí)候加載的呢?我們網(wǎng)上翻看registerHandlerMethod方法,在web服務(wù)器啟動(dòng)時(shí),該方法初始化了urlMap中的數(shù)據(jù)。

通過上述分析,大致可以了解到Spring容器是如何維護(hù)url和方法之間的映射關(guān)系,以及當(dāng)收到請求時(shí)又是如何將請求匹配到正確的方法的。

至于沒有分析到的當(dāng)類和方法都有@RequestMapping注解時(shí)觸發(fā)的combine操作究竟做了什么,當(dāng)找到多個(gè)匹配方法是又是如何通過比較器進(jìn)行排序的,我們下次再分析。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java中判斷對象是否為空的不同方法

    Java中判斷對象是否為空的不同方法

    在Java編程中,經(jīng)常會(huì)遇到判斷對象是否為空的情況,本篇將深入探討Java中判斷對象是否為空的不同方法,包括使用條件判斷、使用Java 8的Optional類、使用Apache Commons Lang庫等,通過詳細(xì)的解釋和舉例說明,幫助讀者正確處理空對象問題,需要的朋友一起看看吧
    2023-11-11
  • 淺談junit4單元測試高級用法

    淺談junit4單元測試高級用法

    這篇文章主要介紹了淺談junit4單元測試高級用法,小編覺得挺不錯(cuò)的,在這里分享給大家,需要的朋友可以參考下。
    2017-10-10
  • java中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼

    java中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼

    java中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼,需要的朋友可以參考一下
    2013-03-03
  • java中你的項(xiàng)目應(yīng)該如何正確分層

    java中你的項(xiàng)目應(yīng)該如何正確分層

    這篇文章主要介紹了java中你的項(xiàng)目應(yīng)該如何正確分層,業(yè)務(wù)分層對于代碼規(guī)范是比較重要,決定著以后的代碼是否可復(fù)用,感興趣的可以了解一下
    2021-04-04
  • Docker容器使用宿主機(jī)上的mongod/redis等服務(wù)詳解

    Docker容器使用宿主機(jī)上的mongod/redis等服務(wù)詳解

    這篇文章主要介紹了Docker容器使用宿主機(jī)上的mongod/redis等服務(wù)詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • 深入學(xué)習(xí)Spring Cloud-Ribbon

    深入學(xué)習(xí)Spring Cloud-Ribbon

    這篇文章主要介紹了Spring Cloud-Ribbon的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友一起看看吧
    2021-03-03
  • SpringBoot+MQTT+apollo實(shí)現(xiàn)訂閱發(fā)布功能的示例

    SpringBoot+MQTT+apollo實(shí)現(xiàn)訂閱發(fā)布功能的示例

    這篇文章主要介紹了SpringBoot+MQTT+apollo實(shí)現(xiàn)訂閱發(fā)布功能的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • POST方法給@RequestBody傳參數(shù)失敗的解決及原因分析

    POST方法給@RequestBody傳參數(shù)失敗的解決及原因分析

    這篇文章主要介紹了POST方法給@RequestBody傳參數(shù)失敗的解決及原因分析,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Java獲取字符串編碼格式實(shí)現(xiàn)思路

    Java獲取字符串編碼格式實(shí)現(xiàn)思路

    文件編碼的格式?jīng)Q定了文件可存儲(chǔ)的字符類型,所以得到文件的類型至關(guān)重要,下文筆者講述獲取一個(gè)文本文件的格式信息的方法分享及java字符串編碼格式實(shí)現(xiàn),感興趣的朋友一起看看吧
    2022-09-09
  • springMVC+ajax實(shí)現(xiàn)文件上傳且?guī)нM(jìn)度條實(shí)例

    springMVC+ajax實(shí)現(xiàn)文件上傳且?guī)нM(jìn)度條實(shí)例

    本篇文章主要介紹了springMVC+ajax實(shí)現(xiàn)文件上傳且?guī)нM(jìn)度條實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下。
    2017-01-01

最新評論