最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring源碼之請(qǐng)求路徑匹配路由方式

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

請(qǐng)求路徑匹配路由

在spring中,當(dāng)一個(gè)請(qǐng)求過來的時(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ù)你的請(qǐng)求path和request去查找合適的method了。在項(xiàng)目啟動(dòng)的時(shí)候,Spring就把路徑和對(duì)應(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ì)查看各種條件是否匹配,包括,請(qǐng)求方法methods,參數(shù)params,請(qǐng)求頭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 將請(qǐng)求找到匹配的處理

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

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

在web服務(wù)器啟動(dòng)時(shí),Spring容器中會(huì)保存一個(gè)map的數(shù)據(jù)結(jié)構(gòu),里邊記錄這controller和url請(qǐng)求中的對(duì)應(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))){
                        //如果有上述注解,則需要保存對(duì)應(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) {
                //注冊(cè)HandlerMethod,在里邊進(jìn)行一些重復(fù)驗(yàn)證等
  registerHandlerMethod(handler, method, mappings.get(method));
 }
}

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

@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對(duì)象
   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對(duì)象的呢?

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);
  }
 
//將沒有*號(hào)和問好的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ù)匹配瀏覽器請(qǐng)求用哪個(gè)方法來處理時(shí)會(huì)重點(diǎn)用到。

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

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

我們從DispatcherServlet的doService方法來看起,該方法中,最終會(huì)調(diào)用到AbstractHandlerMethodMapping類的lookupHandlerMethod方法來確定這個(gè)請(qǐng)求應(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)收到請(qǐng)求時(shí)又是如何將請(qǐng)求匹配到正確的方法的。

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

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

相關(guān)文章

  • springboot使用dubbo和zookeeper代碼實(shí)例

    springboot使用dubbo和zookeeper代碼實(shí)例

    這篇文章主要介紹了springboot使用dubbo和zookeeper代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • IDEA中的.iml文件和.idea文件夾

    IDEA中的.iml文件和.idea文件夾

    這篇文章主要介紹了IDEA中的.iml文件和.idea文件夾,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • profiles.active多環(huán)境開發(fā)、測(cè)試、部署過程

    profiles.active多環(huán)境開發(fā)、測(cè)試、部署過程

    這篇文章主要介紹了profiles.active多環(huán)境開發(fā)、測(cè)試、部署,主要講如何使用profiles.active這個(gè)變量,讓我們?cè)陂_發(fā)過程快速切換環(huán)境配置,以及如何使一個(gè)部署適配各種不同的環(huán)境,需要的朋友可以參考下
    2023-03-03
  • JVM原理之類加載的全過程

    JVM原理之類加載的全過程

    文章詳細(xì)介紹了Java類加載過程,包括加載、鏈接、初始化、使用和卸載五個(gè)階段,并解釋了符號(hào)引用和直接引用的區(qū)別,以及類變量和實(shí)例變量的區(qū)別,此外,還介紹了Class.forName()方法的作用和使用場(chǎng)景
    2025-01-01
  • SpringBoot過濾器的使用

    SpringBoot過濾器的使用

    過濾器是對(duì)數(shù)據(jù)進(jìn)行過濾,預(yù)處理過程,當(dāng)我們?cè)L問網(wǎng)站時(shí),有時(shí)候會(huì)發(fā)布一些敏感信息,發(fā)完以后有的會(huì)用*替代,還有就是登陸權(quán)限控制等,一個(gè)資源,沒有經(jīng)過授權(quán),肯定是不能讓用戶隨便訪問的,這個(gè)時(shí)候,也可以用到過濾器,需要的朋友可以參考一下
    2021-11-11
  • springboot+maven多環(huán)境動(dòng)態(tài)配置及編譯失敗的解決方案(步驟詳解)

    springboot+maven多環(huán)境動(dòng)態(tài)配置及編譯失敗的解決方案(步驟詳解)

    這篇文章主要介紹了springboot+maven多環(huán)境動(dòng)態(tài)配置及編譯失敗的解決方案,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-11-11
  • 高內(nèi)聚低耦合法則實(shí)例解析

    高內(nèi)聚低耦合法則實(shí)例解析

    這篇文章主要介紹了高內(nèi)聚低耦合法則實(shí)例代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • springboot接入deepseek深度求索代碼示例(java版)

    springboot接入deepseek深度求索代碼示例(java版)

    這篇文章主要介紹了springboot接入deepseek深度求索的相關(guān)資料,包括創(chuàng)建APIKey,封裝詢問工具方法,傳入問題,調(diào)用方法,但發(fā)現(xiàn)只能回答簡(jiǎn)單問題,需要的朋友可以參考下
    2025-01-01
  • java異步執(zhí)行代碼處理方法(先返回結(jié)果,后執(zhí)行代碼)

    java異步執(zhí)行代碼處理方法(先返回結(jié)果,后執(zhí)行代碼)

    這篇文章主要給大家介紹了關(guān)于java異步執(zhí)行代碼處理方法的相關(guān)資料,先返回結(jié)果,后執(zhí)行代碼,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • 詳解Spring Boot 2.0.2+Ajax解決跨域請(qǐng)求的問題

    詳解Spring Boot 2.0.2+Ajax解決跨域請(qǐng)求的問題

    這篇文章主要介紹了詳解Spring Boot 2.0.2+Ajax解決跨域請(qǐng)求的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03

最新評(píng)論

凤山县| 海城市| 馆陶县| 黑龙江省| 成武县| 油尖旺区| 余江县| 铁岭县| 黎城县| 石泉县| 和静县| 阳东县| 磐石市| 根河市| 永平县| 含山县| 榆社县| 海口市| 四平市| 尼木县| 苍梧县| 吕梁市| 大悟县| 芦溪县| 西安市| 蚌埠市| 屯昌县| 肥乡县| 清新县| 兴海县| 太湖县| 清远市| 玛多县| 通河县| 固始县| 治县。| 专栏| 黄梅县| 博乐市| 邛崃市| 大荔县|