Spring源碼之請求路徑匹配路由方式
請求路徑匹配路由
在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為HandlerMethodurlMap
: key為沒有*和?的pattern(比如/test/test1)value為RequestMappingInfonameMap
: 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中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼
java中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼,需要的朋友可以參考一下2013-03-03java中你的項(xiàng)目應(yīng)該如何正確分層
這篇文章主要介紹了java中你的項(xiàng)目應(yīng)該如何正確分層,業(yè)務(wù)分層對于代碼規(guī)范是比較重要,決定著以后的代碼是否可復(fù)用,感興趣的可以了解一下2021-04-04Docker容器使用宿主機(jī)上的mongod/redis等服務(wù)詳解
這篇文章主要介紹了Docker容器使用宿主機(jī)上的mongod/redis等服務(wù)詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11深入學(xué)習(xí)Spring Cloud-Ribbon
這篇文章主要介紹了Spring Cloud-Ribbon的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友一起看看吧2021-03-03SpringBoot+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-06POST方法給@RequestBody傳參數(shù)失敗的解決及原因分析
這篇文章主要介紹了POST方法給@RequestBody傳參數(shù)失敗的解決及原因分析,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10springMVC+ajax實(shí)現(xiàn)文件上傳且?guī)нM(jìn)度條實(shí)例
本篇文章主要介紹了springMVC+ajax實(shí)現(xiàn)文件上傳且?guī)нM(jìn)度條實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01