亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

Spring MVC學習教程之RequestMappingHandlerMapping匹配

2024-07-14 08:42:53
字體:
來源:轉載
供稿:網友

前言

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

  • 通過request查找對應的HandlerMethod,即當前request具體是由Controller中的哪個方法進行處理;
  • 查找當前系統中的Interceptor,將其與HandlerMethod封裝為一個HandlerExecutionChain。

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

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

1.整體封裝結構

RequestMappingHandlerMapping實現了HandlerMapping接口,該接口的主要方法如下:

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

這里我們直接看RequestMappingHandlerMapping是如何實現該接口的:

@Override@Nullablepublic 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) {  // 如果找不到處理方法,則獲取自定義的默認handler  handler = getDefaultHandler(); } if (handler == null) {  return null; } if (handler instanceof String) {  // 如果獲取的handler是String類型的,則在當前BeanFactory中獲取該名稱的bean,  // 并將其作為handler返回  String handlerName = (String) handler;  handler = obtainApplicationContext().getBean(handlerName); } // 獲取當前系統中配置的Interceptor,將其與handler一起封裝為一個HandlerExecutionChain HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); // 這里CorsUtils.isCorsRequest()方法判斷的是當前請求是否為一個跨域的請求,如果是一個跨域的請求, // 則將跨域相關的配置也一并封裝到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首先會獲取當前request對應的handler,然后將其與Interceptor一起封裝為一個HandlerExecutionChain對象。這里在進行封裝的時候,Spring會對當前request是否為跨域請求進行判斷,如果是跨域請求,則將相關的跨域配置封裝到HandlerExecutionChain中,關于跨域請求,讀者可以閱讀跨域資源共享 CORS 詳解。

2. 獲取HandlerMethod

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

@Overrideprotected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 獲取當前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是一個String類型的,則在BeanFactory中查找該bean,  // 并將其封裝為一個HandlerMethod對象  return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally {  // 釋放當前注冊的Mapping的讀鎖  this.mappingRegistry.releaseReadLock(); }}

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

@Nullableprotected HandlerMethod lookupHandlerMethod(String lookupPath,   HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // 通過uri直接在注冊的RequestMapping中獲取對應的RequestMappingInfo列表,需要注意的是, // 這里進行查找的方式只是通過url進行查找,但是具體哪些RequestMappingInfo是匹配的,還需要進一步過濾 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) {  // 對獲取到的RequestMappingInfo進行進一步過濾,并且將過濾結果封裝為一個Match列表  addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) {  // 如果無法通過uri進行直接匹配,則對所有的注冊的RequestMapping進行匹配,這里無法通過uri  // 匹配的情況主要有三種:  // ①在RequestMapping中定義的是PathVariable,如/user/detail/{id};  // ②在RequestMapping中定義了問號表達式,如/user/?etail;  // ③在RequestMapping中定義了*或**匹配,如/user/detail/**  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) {   // 如果匹配結果不止一個,首先會判斷是否是跨域請求,如果是,   // 則返回PREFLIGHT_AMBIGUOUS_MATCH,如果不是,則會判斷前兩個匹配程度是否相同,   // 如果相同則拋出異常   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 + "}");   }  }  // 這里主要是對匹配結果的一個處理,主要包含對傳入參數和返回的MediaType的處理  handleMatch(bestMatch.mapping, lookupPath, request);  return bestMatch.handlerMethod; } else {  // 如果匹配結果是空的,則對所有注冊的Mapping進行遍歷,判斷當前request具體是哪種情況導致  // 的無法匹配:①RequestMethod無法匹配;②Consumes無法匹配;③Produces無法匹配;  // ④Params無法匹配  return handleNoMatch(this.mappingRegistry.getMappings().keySet(),    lookupPath, request); }}

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

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進行遍歷,然后將request分別于每個RequestMappingInfo進行匹配,如果匹配上了,其返回值就不為空,最后將所有的匹配結果返回。如下是getMatchingMapping()方法的源碼(其最終調用的是RequestMappingInfo.getMatchingCondition()方法):

@Override@Nullablepublic RequestMappingInfo getMatchingCondition(HttpServletRequest request) { // 判斷request請求的類型是否與當前RequestMethod匹配 RequestMethodsRequestCondition methods =   this.methodsCondition.getMatchingCondition(request); // 判斷request請求的參數是否與RequestMapping中params參數配置的一致 ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request); // 判斷request請求的headers是否與RequestMapping中headers參數配置的一致 HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request); // 判斷request的請求體類型是否與RequestMapping中配置的consumes參數配置的一致 ConsumesRequestCondition consumes =   this.consumesCondition.getMatchingCondition(request); // 判斷當前RequestMapping將要返回的請求體類型是否與request中Accept的header指定的一致 ProducesRequestCondition produces =   this.producesCondition.getMatchingCondition(request); // 對于上述幾個判斷,如果匹配上了,那么其返回值都不會為空,因而這里會對每個返回值都進行判斷, // 如果有任意一個為空,則說明沒匹配上,那么就返回null if (methods == null || params == null || headers == null   || consumes == null || produces == null) {  return null; } // 對于前面的匹配,都是一些靜態屬性的匹配,其中最重要的uri的匹配,主要是正則匹配, // 就是在下面這個方法中進行的 PatternsRequestCondition patterns =   this.patternsCondition.getMatchingCondition(request); // 如果URI沒匹配上,則返回null if (patterns == null) {  return null; } // 這里主要是對用戶自定義的匹配條件進行匹配 RequestConditionHolder custom =   this.customConditionHolder.getMatchingCondition(request); if (custom == null) {  return null; } // 如果上述所有條件都匹配上了,那么就將匹配結果封裝為一個RequestMappingInfo返回 return new RequestMappingInfo(this.name, patterns, methods, params, headers,   consumes, produces, custom.getCondition());}

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

3. Interceptor的封裝

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

protected HandlerExecutionChain getHandlerExecutionChain(Object handler,   HttpServletRequest request) { // 將當前handler封裝到HandlerExecutionChain對象中 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?  (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); // 獲取當前request的URI,用于MappedInterceptor的匹配 String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); // 對當前所有注冊的Interceptor進行遍歷,如果其是MappedInterceptor類型,則調用其matches() // 方法,判斷當前Interceptor是否能夠應用于該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 {   // 如果當前Interceptor不是MappedInterceptor類型,則直接將其添加到   // HandlerExecutionChain中   chain.addInterceptor(interceptor);  } } return chain;}

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

4. 小結

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VeVb武林網的支持。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美不卡视频一区发布| 国产精品福利在线观看| 国产精品一区二区电影| 欧美色xxxx| 91中文精品字幕在线视频| 国产一区二区三区在线视频| 欧美黑人性生活视频| 成人黄色激情网| 久久99国产精品久久久久久久久| 日韩av中文字幕在线免费观看| 奇米一区二区三区四区久久| 国产精品海角社区在线观看| 欧美中文在线视频| 国产在线观看不卡| 91福利视频网| 97高清免费视频| 亚洲人永久免费| 亚洲bt欧美bt日本bt| 欧美www视频在线观看| 国产成人精品在线观看| 欧美日韩国产一区中文午夜| 国产欧美va欧美va香蕉在| 麻豆国产精品va在线观看不卡| 亚洲精品国产福利| 亚洲国产天堂久久综合网| 国产精品尤物福利片在线观看| 久久久久久成人精品| 亚洲精品女av网站| 国产不卡一区二区在线播放| 欧美精品一区二区三区国产精品| 久99九色视频在线观看| 欧美成人中文字幕| 国产成人拍精品视频午夜网站| 91精品久久久久久久久久入口| 色诱女教师一区二区三区| 亚洲japanese制服美女| 亚洲欧美一区二区三区情侣bbw| 日本午夜在线亚洲.国产| 亚洲二区中文字幕| 精品女同一区二区三区在线播放| 亚洲深夜福利网站| 欧美精品aaa| 日本国产一区二区三区| 欧美激情一区二区三区在线视频观看| 精品一区二区三区三区| 色999日韩欧美国产| 亚洲精品资源在线| 亚洲精品福利资源站| 亚洲精品日韩av| 在线性视频日韩欧美| 在线看欧美日韩| 久久视频中文字幕| 亚洲jizzjizz日本少妇| 欧美人与性动交a欧美精品| 欧美国产亚洲视频| 欧美日韩亚洲系列| 国产精品久久久久久超碰| 午夜精品久久久久久久男人的天堂| 日韩精品中文字幕在线观看| 美女精品视频一区| 久久久精品中文字幕| 4444欧美成人kkkk| 国产一区二区三区在线免费观看| 欧美激情二区三区| 国产精品久久久久久久久久久新郎| 亚洲国产精品福利| 国产成人在线播放| 亚洲老头老太hd| 国产精品jvid在线观看蜜臀| 欧美电影电视剧在线观看| 国产男女猛烈无遮挡91| 欧美黄色片视频| 亚洲男人7777| 欧美有码在线观看视频| 亚洲国产精品系列| 亚洲激情 国产| 日韩美女视频在线观看| xxx一区二区| 中文字幕亚洲欧美日韩2019| 欧美日韩aaaa| 91久久精品美女高潮| 亚洲免费精彩视频| 国产在线精品成人一区二区三区| 97精品一区二区三区| 在线播放国产一区中文字幕剧情欧美| 精品免费在线观看| 国产区精品在线观看| 狠狠躁18三区二区一区| 国产精品一区二区三区成人| 久久久免费精品视频| 乱亲女秽乱长久久久| 国产精品视频专区| 欧美另类暴力丝袜| 琪琪第一精品导航| 秋霞av国产精品一区| 色综合视频一区中文字幕| 国产一区二区三区精品久久久| 成人激情视频小说免费下载| 成人免费视频xnxx.com| 91精品国产自产在线老师啪| 欧美精品一区二区三区国产精品| 欧美电影《睫毛膏》| 国产精品成人av在线| 7m精品福利视频导航| www.日韩欧美| 欧美日韩免费网站| 欧美激情奇米色| 欧美激情2020午夜免费观看| 91九色在线视频| 亚洲一区二区三区香蕉| 日韩在线小视频| 国产视频精品va久久久久久| y97精品国产97久久久久久| 亚洲va国产va天堂va久久| 欧美性高潮床叫视频| 国产精品欧美日韩久久| 久久精品91久久香蕉加勒比| 不卡av日日日| 久久久人成影片一区二区三区观看| 国产精品大陆在线观看| 亚洲欧美日韩直播| 欧美电影《睫毛膏》| 欧美性xxxx极品hd欧美风情| 少妇高潮久久77777| 欧美亚洲成人网| 国产精品吊钟奶在线| 国产日本欧美一区| 91精品国产乱码久久久久久久久| 91国产视频在线播放| 亚洲日韩欧美视频| 亚洲一区二区免费| 国产精品美女在线观看| 久久亚洲精品小早川怜子66| 中文字幕国内精品| 国产精品精品一区二区三区午夜版| 国产主播欧美精品| 日韩av日韩在线观看| 51精品在线观看| 热久久美女精品天天吊色| 亚洲一区二区三区四区在线播放| 日韩中文字幕在线精品| 国产精品一区二区三区久久| 亚洲网站在线看| 欧美大片免费观看在线观看网站推荐| 久久精品小视频| 成人中文字幕在线观看| 欧美伊久线香蕉线新在线| 伊人伊成久久人综合网小说| 亚洲国产一区二区三区四区| 中文欧美日本在线资源| 国产精品美女av| 久久精品国产99国产精品澳门| 国产精品第3页| 国产日韩欧美在线| 蜜臀久久99精品久久久无需会员| 中文字幕亚洲欧美日韩在线不卡| 国产免费一区视频观看免费| 狠狠躁夜夜躁久久躁别揉| 92福利视频午夜1000合集在线观看| 懂色av影视一区二区三区| 高清一区二区三区四区五区| 91夜夜揉人人捏人人添红杏| 最近2019年好看中文字幕视频| 国产精品第一页在线|