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

Spring MVC學(xué)習(xí)教程之RequestMappingHandlerMapping匹配

 更新時(shí)間:2018年11月18日 10:55:01   作者:愛寶貝丶  
這篇文章主要給大家介紹了關(guān)于Spring MVC學(xué)習(xí)教程之RequestMappingHandlerMapping匹配的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

對于RequestMappingHandlerMapping,使用Spring的同學(xué)基本都不會陌生,該類的作用有兩個(gè):

  • 通過request查找對應(yīng)的HandlerMethod,即當(dāng)前request具體是由Controller中的哪個(gè)方法進(jìn)行處理;
  • 查找當(dāng)前系統(tǒng)中的Interceptor,將其與HandlerMethod封裝為一個(gè)HandlerExecutionChain。

本文主要講解RequestMappingHandlerMapping是如何獲取HandlerMethod和Interceptor,并且將其封裝為HandlerExecutionChain的。

下面話不多說了,來一起看看詳細(xì)的介紹吧

1.整體封裝結(jié)構(gòu)

RequestMappingHandlerMapping實(shí)現(xiàn)了HandlerMapping接口,該接口的主要方法如下:

public interface HandlerMapping {
 // 通過request獲取HandlerExecutionChain對象
 HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

這里我們直接看RequestMappingHandlerMapping是如何實(shí)現(xiàn)該接口的:

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) 
 throws Exception {
 // 通過request獲取具體的處理bean,這里handler可能有兩種類型:HandlerMethod和String。
 // 如果是String類型,那么就在BeanFactory中查找該String類型的bean,需要注意的是,返回的
 // bean如果是需要使用RequestMappingHandlerAdapter處理,那么也必須是HandlerMethod類型的
 Object handler = getHandlerInternal(request);
 if (handler == null) {
  // 如果找不到處理方法,則獲取自定義的默認(rèn)handler
  handler = getDefaultHandler();
 }
 if (handler == null) {
  return null;
 }
 if (handler instanceof String) {
  // 如果獲取的handler是String類型的,則在當(dāng)前BeanFactory中獲取該名稱的bean,
  // 并將其作為handler返回
  String handlerName = (String) handler;
  handler = obtainApplicationContext().getBean(handlerName);
 }

 // 獲取當(dāng)前系統(tǒng)中配置的Interceptor,將其與handler一起封裝為一個(gè)HandlerExecutionChain
 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
 // 這里CorsUtils.isCorsRequest()方法判斷的是當(dāng)前請求是否為一個(gè)跨域的請求,如果是一個(gè)跨域的請求,
 // 則將跨域相關(guān)的配置也一并封裝到HandlerExecutionChain中
 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;
}

從上面的代碼可以看出,對于HandlerExecutionChain的獲取,RequestMappingHandlerMapping首先會獲取當(dāng)前request對應(yīng)的handler,然后將其與Interceptor一起封裝為一個(gè)HandlerExecutionChain對象。這里在進(jìn)行封裝的時(shí)候,Spring會對當(dāng)前request是否為跨域請求進(jìn)行判斷,如果是跨域請求,則將相關(guān)的跨域配置封裝到HandlerExecutionChain中,關(guān)于跨域請求,讀者可以閱讀跨域資源共享 CORS 詳解。

2. 獲取HandlerMethod

關(guān)于RequestMappingHandlerMapping是如何獲取handler的,其主要在getHandlerInternal()方法中,如下是該方法的源碼:

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
 // 獲取當(dāng)前request的URI
 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
 if (logger.isDebugEnabled()) {
  logger.debug("Looking up handler method for path " + lookupPath);
 }
 // 獲取注冊的Mapping的讀鎖
 this.mappingRegistry.acquireReadLock();
 try {
  // 通過path和request查找具體的HandlerMethod
  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 + "]");
   }
  }
  // 如果獲取到的bean是一個(gè)String類型的,則在BeanFactory中查找該bean,
  // 并將其封裝為一個(gè)HandlerMethod對象
  return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
 } finally {
  // 釋放當(dāng)前注冊的Mapping的讀鎖
  this.mappingRegistry.releaseReadLock();
 }
}

上述方法中,其首先會獲取當(dāng)前request的uri,然后通過uri查找HandlerMethod,并且在最后,會判斷獲取到的HandlerMethod中的bean是否為String類型的,如果是,則在當(dāng)前BeanFactory中查找該名稱的bean,并且將其封裝為HandlerMethod對象。這里我們直接閱讀lookupHandlerMethod()方法:

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, 
  HttpServletRequest request) throws Exception {
 List<Match> matches = new ArrayList<>();
 // 通過uri直接在注冊的RequestMapping中獲取對應(yīng)的RequestMappingInfo列表,需要注意的是,
 // 這里進(jìn)行查找的方式只是通過url進(jìn)行查找,但是具體哪些RequestMappingInfo是匹配的,還需要進(jìn)一步過濾
 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
 if (directPathMatches != null) {
  // 對獲取到的RequestMappingInfo進(jìn)行進(jìn)一步過濾,并且將過濾結(jié)果封裝為一個(gè)Match列表
  addMatchingMappings(directPathMatches, matches, request);
 }
 if (matches.isEmpty()) {
  // 如果無法通過uri進(jìn)行直接匹配,則對所有的注冊的RequestMapping進(jìn)行匹配,這里無法通過uri
  // 匹配的情況主要有三種:
  // ①在RequestMapping中定義的是PathVariable,如/user/detail/{id};
  // ②在RequestMapping中定義了問號表達(dá)式,如/user/?etail;
  // ③在RequestMapping中定義了*或**匹配,如/user/detail/**
  addMatchingMappings(this.mappingRegistry.getMappings().keySet(), 
   matches, request);
 }

 if (!matches.isEmpty()) {
  // 對匹配的結(jié)果進(jìn)行排序,獲取相似度最高的一個(gè)作為結(jié)果返回,這里對相似度的判斷時(shí),
  // 會判斷前兩個(gè)是否相似度是一樣的,如果是一樣的,則直接拋出異常,如果不相同,
  // 則直接返回最高的一個(gè)
  Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
  matches.sort(comparator);
  if (logger.isTraceEnabled()) {
   logger.trace("Found " + matches.size() 
       + " matching mapping(s) for [" + lookupPath + "] : " + matches);
  }
  // 獲取匹配程度最高的一個(gè)匹配結(jié)果
  Match bestMatch = matches.get(0);
  if (matches.size() > 1) {
   // 如果匹配結(jié)果不止一個(gè),首先會判斷是否是跨域請求,如果是,
   // 則返回PREFLIGHT_AMBIGUOUS_MATCH,如果不是,則會判斷前兩個(gè)匹配程度是否相同,
   // 如果相同則拋出異常
   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();
    throw new IllegalStateException("Ambiguous handler methods mapped for" 
     + " HTTP path '" + request.getRequestURL() + "': {" + m1 
     + ", " + m2 + "}");
   }
  }
  // 這里主要是對匹配結(jié)果的一個(gè)處理,主要包含對傳入?yún)?shù)和返回的MediaType的處理
  handleMatch(bestMatch.mapping, lookupPath, request);
  return bestMatch.handlerMethod;
 } else {
  // 如果匹配結(jié)果是空的,則對所有注冊的Mapping進(jìn)行遍歷,判斷當(dāng)前request具體是哪種情況導(dǎo)致
  // 的無法匹配:①RequestMethod無法匹配;②Consumes無法匹配;③Produces無法匹配;
  // ④Params無法匹配
  return handleNoMatch(this.mappingRegistry.getMappings().keySet(), 
   lookupPath, request);
 }
}

這里對于結(jié)果的匹配,首先會通過uri進(jìn)行直接匹配,如果能匹配到,則在匹配結(jié)果中嘗試進(jìn)行RequestMethod,Consumes和Produces等配置的匹配;如果通過uri不能匹配到,則直接對所有定義的RequestMapping進(jìn)行匹配,這里主要是進(jìn)行正則匹配,如果能匹配到。如果能夠匹配到,則對匹配結(jié)果按照相似度進(jìn)行排序,并且對前兩個(gè)結(jié)果相似度進(jìn)行比較,如果相似度一樣,則拋出異常,如果不一樣,則返回相似度最高的一個(gè)匹配結(jié)果。如果無法獲取到匹配結(jié)果,則對所有的匹配結(jié)果進(jìn)行遍歷,判斷當(dāng)前request具體是哪一部分參數(shù)無法匹配到結(jié)果。對于匹配結(jié)果的獲取,主要在addMatchingMappings()方法中,這里我們繼續(xù)閱讀該方法的源碼:

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, 
  HttpServletRequest request) {
 for (T mapping : mappings) {
  T match = getMatchingMapping(mapping, request);
  if (match != null) {
   matches.add(new Match(match, 
    this.mappingRegistry.getMappings().get(mapping)));
  }
 }
}

對于RequestMapping的匹配,這里邏輯比較簡單,就是對所有的RequestMappingInfo進(jìn)行遍歷,然后將request分別于每個(gè)RequestMappingInfo進(jìn)行匹配,如果匹配上了,其返回值就不為空,最后將所有的匹配結(jié)果返回。如下是getMatchingMapping()方法的源碼(其最終調(diào)用的是RequestMappingInfo.getMatchingCondition()方法):

@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
 // 判斷request請求的類型是否與當(dāng)前RequestMethod匹配
 RequestMethodsRequestCondition methods = 
  this.methodsCondition.getMatchingCondition(request);
 // 判斷request請求的參數(shù)是否與RequestMapping中params參數(shù)配置的一致
 ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
 // 判斷request請求的headers是否與RequestMapping中headers參數(shù)配置的一致
 HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
 // 判斷request的請求體類型是否與RequestMapping中配置的consumes參數(shù)配置的一致
 ConsumesRequestCondition consumes = 
  this.consumesCondition.getMatchingCondition(request);
 // 判斷當(dāng)前RequestMapping將要返回的請求體類型是否與request中Accept的header指定的一致
 ProducesRequestCondition produces = 
  this.producesCondition.getMatchingCondition(request);

 // 對于上述幾個(gè)判斷,如果匹配上了,那么其返回值都不會為空,因而這里會對每個(gè)返回值都進(jìn)行判斷,
 // 如果有任意一個(gè)為空,則說明沒匹配上,那么就返回null
 if (methods == null || params == null || headers == null 
  || consumes == null || produces == null) {
  return null;
 }

 // 對于前面的匹配,都是一些靜態(tài)屬性的匹配,其中最重要的uri的匹配,主要是正則匹配,
 // 就是在下面這個(gè)方法中進(jìn)行的
 PatternsRequestCondition patterns = 
  this.patternsCondition.getMatchingCondition(request);
 // 如果URI沒匹配上,則返回null
 if (patterns == null) {
  return null;
 }

 // 這里主要是對用戶自定義的匹配條件進(jìn)行匹配
 RequestConditionHolder custom = 
  this.customConditionHolder.getMatchingCondition(request);
 if (custom == null) {
  return null;
 }

 // 如果上述所有條件都匹配上了,那么就將匹配結(jié)果封裝為一個(gè)RequestMappingInfo返回
 return new RequestMappingInfo(this.name, patterns, methods, params, headers, 
  consumes, produces, custom.getCondition());
}

可以看到,對于一個(gè)RequestMapping的匹配,主要包括:RequestMethod,Params,Headers,Consumes,Produces,Uri和自定義條件的匹配,如果這幾個(gè)條件都匹配上了,才能表明當(dāng)前RequestMapping與request匹配上了。

3. Interceptor的封裝

關(guān)于Inteceptor的封裝,由前述第一點(diǎn)可以看出,其主要在getHandlerExecutionChain()方法中,如下是該方法的源碼:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, 
  HttpServletRequest request) {
 // 將當(dāng)前handler封裝到HandlerExecutionChain對象中
 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
  (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

 // 獲取當(dāng)前request的URI,用于MappedInterceptor的匹配
 String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
 // 對當(dāng)前所有注冊的Interceptor進(jìn)行遍歷,如果其是MappedInterceptor類型,則調(diào)用其matches()
 // 方法,判斷當(dāng)前Interceptor是否能夠應(yīng)用于該request,如果可以,則添加到HandlerExecutionChain中
 for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
  if (interceptor instanceof MappedInterceptor) {
   MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
   if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
    chain.addInterceptor(mappedInterceptor.getInterceptor());
   }
  } else {
   // 如果當(dāng)前Interceptor不是MappedInterceptor類型,則直接將其添加到
   // HandlerExecutionChain中
   chain.addInterceptor(interceptor);
  }
 }
 return chain;
}

對于攔截器,理論上,Spring是會將所有的攔截器都進(jìn)行一次調(diào)用,對于是否需要進(jìn)行攔截,都是用戶自定義實(shí)現(xiàn)的。這里如果對于URI有特殊的匹配,可以使用MappedInterceptor,然后實(shí)現(xiàn)其matches()方法,用于判斷當(dāng)前MappedInterceptor是否能夠應(yīng)用于當(dāng)前request。

4. 小結(jié)

本文首先講解了Spring是如何通過request進(jìn)行匹配,從而找到具體處理當(dāng)前請求的RequestMapping的,然后講解了Spring是如何封裝Interceptor,將HandlerMethod和Interceptor封裝為一個(gè)HandlerExecutionChain的。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • SpringBoot使用?Sleuth?進(jìn)行分布式跟蹤的過程分析

    SpringBoot使用?Sleuth?進(jìn)行分布式跟蹤的過程分析

    Spring Boot Sleuth是一個(gè)分布式跟蹤解決方案,它可以幫助您在分布式系統(tǒng)中跟蹤請求并分析性能問題,Spring Boot Sleuth是Spring Cloud的一部分,它提供了分布式跟蹤的功能,本文將介紹如何在Spring Boot應(yīng)用程序中使用Sleuth進(jìn)行分布式跟蹤,感興趣的朋友一起看看吧
    2023-10-10
  • Springboot實(shí)現(xiàn)WebMvcConfigurer接口定制mvc配置詳解

    Springboot實(shí)現(xiàn)WebMvcConfigurer接口定制mvc配置詳解

    這篇文章主要介紹了Springboot實(shí)現(xiàn)WebMvcConfigurer接口定制mvc配置詳解,spring?boot拋棄了傳統(tǒng)xml配置文件,通過配置類(標(biāo)注@Configuration的類,@Configuration配置類相當(dāng)于一個(gè)xml配置文件)以JavaBean形式進(jìn)行相關(guān)配置,需要的朋友可以參考下
    2023-09-09
  • Springboot項(xiàng)目Maven依賴沖突的問題解決

    Springboot項(xiàng)目Maven依賴沖突的問題解決

    使用Spring Boot和Maven進(jìn)行項(xiàng)目開發(fā)時(shí),依賴沖突是一個(gè)常見的問題,本文就來介紹一下Springboot項(xiàng)目Maven依賴沖突的問題解決,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-07-07
  • java實(shí)現(xiàn)實(shí)時(shí)通信聊天程序

    java實(shí)現(xiàn)實(shí)時(shí)通信聊天程序

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)實(shí)時(shí)通信聊天程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • gateway網(wǎng)關(guān)與前端請求跨域問題的解決方案

    gateway網(wǎng)關(guān)與前端請求跨域問題的解決方案

    這篇文章主要介紹了gateway網(wǎng)關(guān)與前端請求跨域問題的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • java中Hashmap的get方法使用

    java中Hashmap的get方法使用

    這篇文章主要介紹了java中Hashmap的get方法使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 關(guān)于webLucene 安裝方法

    關(guān)于webLucene 安裝方法

    webLucene是一個(gè)基于開源項(xiàng)目lucene實(shí)現(xiàn)站內(nèi)搜索的工具,關(guān)于它的安裝,百度得到的大多是一樣的,按照步驟也能正確安裝并運(yùn)行,需要注意的問題是
    2009-06-06
  • SpringMVC中@controllerAdvice注解的詳細(xì)解釋

    SpringMVC中@controllerAdvice注解的詳細(xì)解釋

    剛接觸SpringMVC應(yīng)該很少會見到這個(gè)注解,其實(shí)它的作用非常大,下面這篇文章主要給大家介紹了關(guān)于SpringMVC中@controllerAdvice注解的相關(guān)資料,需要的朋友可以參考下
    2022-02-02
  • springcloud集成zookeeper的方法示例

    springcloud集成zookeeper的方法示例

    這篇文章主要介紹了springcloud集成zookeeper的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04
  • 詳解SpringBoot實(shí)現(xiàn)事件同步與異步監(jiān)聽

    詳解SpringBoot實(shí)現(xiàn)事件同步與異步監(jiān)聽

    這篇文章主要通過示例為大家詳細(xì)介紹了SpringBoot中的事件的用法和原理以及如何實(shí)現(xiàn)事件同步與異步監(jiān)聽,快跟隨小編一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06

最新評論

韶关市| 开鲁县| 若羌县| 嘉鱼县| 澎湖县| 梅河口市| 那曲县| 奎屯市| 房产| 浙江省| 安远县| 延吉市| 唐河县| 安义县| 诸城市| 新民市| 彩票| 辽阳市| 黎城县| 峨山| 都安| 通渭县| 大悟县| 额济纳旗| 曲松县| 亳州市| 镇原县| 浏阳市| 长岭县| 江孜县| 松原市| 遵义市| 七台河市| 甘肃省| 濮阳县| 大化| 嵊泗县| 高陵县| 吉水县| 宁晋县| 桦川县|