前面跟小夥伴們分享了 SpringMVC 一個大體的初始化流程以及請求的大體處理流程,在請求處理過程當中,涉及到九大組件,分別是:html
這些組件相信小夥伴們在平常開發中多多少少都有涉及到,若是你對這些組件感到陌生,能夠在公衆號後臺回覆 ssm,免費獲取鬆哥的入門視頻教程。java
那麼接下來的幾篇文章,鬆哥想和你們深刻分析這九大組件,從用法到源碼,挨個分析,今天咱們就先來看看這九大組件中的第一個 HandlerMapping。web
HandlerMapping 叫作處理器映射器,它的做用就是根據當前 request 找到對應的 Handler 和 Interceptor,而後封裝成一個 HandlerExecutionChain 對象返回,咱們來看下 HandlerMapping 接口:spring
public interface HandlerMapping { String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler"; @Deprecated String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath"; String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping"; String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern"; String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping"; String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables"; String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables"; String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes"; default boolean usesPathPatterns() { return false; } @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }
能夠看到,除了一堆聲明的常量外,其實就一個須要實現的方法 getHandler,該方法的返回值就是咱們所瞭解到的 HandlerExecutionChain。跨域
HandlerMapping 的繼承關係以下:數組
這個繼承關係雖然看着有點繞,其實仔細觀察就兩大類:mvc
其餘的都是一些輔助接口。app
AbstractHandlerMethodMapping 體系下的都是根據方法名進行匹配的,而 AbstractUrlHandlerMapping 體系下的都是根據 URL 路徑進行匹配的,這二者有一個共同的父類 AbstractHandlerMapping,接下來咱們就對這三個關鍵類進行詳細分析。cors
AbstractHandlerMapping 實現了 HandlerMapping 接口,不管是經過 URL 進行匹配仍是經過方法名進行匹配,都是經過繼承 AbstractHandlerMapping 來實現的,因此 AbstractHandlerMapping 所作的事情其實就是一些公共的事情,將以一些須要具體處理的事情則交給子類去處理,這其實就是典型的模版方法模式。jsp
AbstractHandlerMapping 間接繼承自 ApplicationObjectSupport,並重寫了 initApplicationContext 方法(其實該方法也是一個模版方法),這也是 AbstractHandlerMapping 的初始化入口方法,咱們一塊兒來看下:
@Override protected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); detectMappedInterceptors(this.adaptedInterceptors); initInterceptors(); }
三個方法都和攔截器有關。
extendInterceptors
protected void extendInterceptors(List<Object> interceptors) { }
extendInterceptors 是一個模版方法,能夠在子類中實現,子類實現了該方法以後,能夠對攔截器進行添加、刪除或者修改,不過在 SpringMVC 的具體實現中,其實這個方法並無在子類中進行實現。
detectMappedInterceptors
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors( obtainApplicationContext(), MappedInterceptor.class, true, false).values()); }
detectMappedInterceptors 方法會從 SpringMVC 容器以及 Spring 容器中查找全部 MappedInterceptor 類型的 Bean,查找到以後添加到 mappedInterceptors 屬性中(其實就是全局的 adaptedInterceptors 屬性)。通常來講,咱們定義好一個攔截器以後,還要在 XML 文件中配置該攔截器,攔截器以及各類配置信息,最終就會被封裝成一個 MappedInterceptor 對象。
initInterceptors
protected void initInterceptors() { if (!this.interceptors.isEmpty()) { for (int i = 0; i < this.interceptors.size(); i++) { Object interceptor = this.interceptors.get(i); if (interceptor == null) { throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null"); } this.adaptedInterceptors.add(adaptInterceptor(interceptor)); } } }
initInterceptors 方法主要是進行攔截器的初始化操做,具體內容是將 interceptors 集合中的攔截器添加到 adaptedInterceptors 集合中。
至此,咱們看到,全部攔截器最終都會被存入 adaptedInterceptors 變量中。
AbstractHandlerMapping 的初始化其實也就是攔截器的初始化過程。
爲何 AbstractHandlerMapping 中對攔截器如此重視呢?其實不是重視,你們想一想,AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping 最大的區別在於查找處理器的區別,一旦處理器找到了,再去找攔截器,可是攔截器都是統一的,並無什麼明顯區別,因此攔截器就統一在 AbstractHandlerMapping 中進行處理,而不會去 AbstractUrlHandlerMapping 或者 AbstractHandlerMethodMapping 中處理。
接下來咱們再來看看 AbstractHandlerMapping#getHandler 方法,看看處理器是如何獲取到的:
@Override @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // Ensure presence of cached lookupPath for interceptors and others if (!ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); } HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = getCorsConfiguration(handler, request); if (getCorsConfigurationSource() != null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig != null ? globalConfig.combine(config) : config); } if (config != null) { config.validateAllowCredentials(); } executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
這個方法的執行流程是這樣的:
接下來咱們再來看看第五步的 getHandlerExecutionChain 方法的執行邏輯,正是在這個方法裏邊把 handler 變成了 HandlerExecutionChain:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); for (HandlerInterceptor interceptor : this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(request)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }
這裏直接根據已有的 handler 建立一個新的 HandlerExecutionChain 對象,而後遍歷 adaptedInterceptors 集合,該集合裏存放的都是攔截器,若是攔截器的類型是 MappedInterceptor,則調用 matches 方法去匹配一下,看一下是不是攔截當前請求的攔截器,若是是,則調用 chain.addInterceptor 方法加入到 HandlerExecutionChain 對象中;若是就是一個普通攔截器,則直接加入到 HandlerExecutionChain 對象中。
這就是 AbstractHandlerMapping#getHandler 方法的大體邏輯,能夠看到,這裏留了一個模版方法 getHandlerInternal 在子類中實現,接下來咱們就來看看它的子類。
AbstractUrlHandlerMapping,看名字就知道,都是按照 URL 地址來進行匹配的,它的原理就是將 URL 地址與對應的 Handler 保存在同一個 Map 中,當調用 getHandlerInternal 方法時,就根據請求的 URL 去 Map 中找到對應的 Handler 返回就好了。
這裏咱們就先從他的 getHandlerInternal 方法開始看起:
@Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = initLookupPath(request); Object handler; if (usesPathPatterns()) { RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); handler = lookupHandler(path, lookupPath, request); } else { handler = lookupHandler(lookupPath, request); } if (handler == null) { // We need to care for the default handler directly, since we need to // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. Object rawHandler = null; if (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { // Bean name or resolved handler? if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; }
這就是整個 getHandlerInternal 方法的邏輯,實際上並不難,裏邊主要涉及到 lookupHandler 和 buildPathExposingHandler 兩個方法,須要和你們詳細介紹下,咱們分別來看。
lookupHandler
lookupHandler 有兩個,咱們分別來看。
@Nullable protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception { Object handler = getDirectMatch(lookupPath, request); if (handler != null) { return handler; } // Pattern match? List<String> matchingPatterns = new ArrayList<>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, lookupPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) { matchingPatterns.add(registeredPattern + "/"); } } } String bestMatch = null; Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); bestMatch = matchingPatterns.get(0); } if (bestMatch != null) { handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { throw new IllegalStateException( "Could not find handler for best pattern match [" + bestMatch + "]"); } } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables // for all of them Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; } @Nullable private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception { Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } return null; }
/
的,舉個簡單例子,若是你定義的接口是 /user
,那麼請求路徑能夠是 /user
也能夠 /user/
,這兩種默認都是支持的,因此這裏的 useTrailingSlashMatch 分支主要是處理後面這種狀況,處理方式很簡單,就在 registeredPattern 後面加上 /
而後繼續和請求路徑進行匹配。路由配置 | 優先級 |
---|---|
不含任何特殊符號的路徑,如:配置路由/a/b/c |
第一優先級 |
帶有{} 的路徑,如:/a/{b}/c |
第二優先級 |
帶有正則的路徑,如:/a/{regex:\d{3}}/c |
第三優先級 |
帶有* 的路徑,如:/a/b/* |
第四優先級 |
帶有** 的路徑,如:/a/b/** |
第五優先級 |
最模糊的匹配:/** |
最低優先級 |
/
結尾,若是是以 /
結尾,則去掉結尾的 /
再去 handlerMap 中查找,若是還沒找到,那就該拋異常出來了。若是找到的 handler 是 String 類型的,則再去 Spring 容器中查找對應的 Bean,接下來再調用 validateHandler 方法進行驗證。myroot/*.html
,請求路徑是 myroot/myfile.html
,那麼最終獲取到的就是 myfile.html
。lookupHandler 還有一個重載方法,不過只要你們把這個方法的執行流程搞清楚了,重載方法其實很好理解,這裏鬆哥就再也不贅述了,惟一要說的就是重載方法用了 PathPattern 去匹配 URL 路徑,而這個方法用了 AntPathMatcher 去匹配 URL 路徑。
buildPathExposingHandler
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) { HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler); chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping)); if (!CollectionUtils.isEmpty(uriTemplateVariables)) { chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); } return chain; }
buildPathExposingHandler 方法向 HandlerExecutionChain 中添加了兩個攔截器 PathExposingHandlerInterceptor 和 UriTemplateVariablesHandlerInterceptor,這兩個攔截器在各自的 preHandle 中分別向 request 對象添加了一些屬性,具體添加的屬性小夥伴們能夠自行查看,這個比較簡單,我就很少說了。
在前面的方法中,涉及到一個重要的變量 handlerMap,咱們定義的接口和處理器之間的關係都保存在這個變量中,那麼這個變量是怎麼初始化的呢?這就涉及到 AbstractUrlHandlerMapping 中的另外一個方法 registerHandler:
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { for (String urlPath : urlPaths) { registerHandler(urlPath, beanName); } } protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Object resolvedHandler = handler; if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { throw new IllegalStateException( "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped."); } } else { if (urlPath.equals("/")) { setRootHandler(resolvedHandler); } else if (urlPath.equals("/*")) { setDefaultHandler(resolvedHandler); } else { this.handlerMap.put(urlPath, resolvedHandler); if (getPatternParser() != null) { this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); } } } }
registerHandler(String[],String) 方法有兩個參數,第一個就是定義的請求路徑,第二個參數則是處理器 Bean 的名字,第一個參數是一個數組,那是由於同一個處理器能夠對應多個不一樣的請求路徑。
在重載方法 registerHandler(String,String) 裏邊,完成了 handlerMap 的初始化,具體流程以下:
這就是 AbstractUrlHandlerMapping 的主要工做,其中 registerHandler 將在它的子類中調用。
接下來咱們來看 AbstractUrlHandlerMapping 的子類。
爲了方便處理,SimpleUrlHandlerMapping 中本身定義了一個 urlMap 變量,這樣能夠在註冊以前作一些預處理,例如確保全部的 URL 都是以 /
開始。SimpleUrlHandlerMapping 在定義時重寫了父類的 initApplicationContext 方法,並在該方法中調用了 registerHandlers,在 registerHandlers 中又調用了父類的 registerHandler 方法完成了 handlerMap 的初始化操做:
@Override public void initApplicationContext() throws BeansException { super.initApplicationContext(); registerHandlers(this.urlMap); } protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { if (urlMap.isEmpty()) { logger.trace("No patterns in " + formatMappingName()); } else { urlMap.forEach((url, handler) -> { // Prepend with slash if not already present. if (!url.startsWith("/")) { url = "/" + url; } // Remove whitespace from handler bean name. if (handler instanceof String) { handler = ((String) handler).trim(); } registerHandler(url, handler); }); } }
這塊代碼很簡單,實在沒啥好說的,若是 URL 不是以 /
開頭,則手動給它加上 /
便可。有小夥伴們可能要問了,urlMap 的值從哪裏來?固然是從咱們的配置文件裏邊來呀,像下面這樣:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/aaa" value-ref="/hello"/> </map> </property> </bean>
AbstractDetectingUrlHandlerMapping 也是 AbstractUrlHandlerMapping 的子類,可是它和 SimpleUrlHandlerMapping 有一些不同的地方。
不同的是哪裏呢?
AbstractDetectingUrlHandlerMapping 會自動查找到 SpringMVC 容器以及 Spring 容器中的全部 beanName,而後根據 beanName 解析出對應的 URL 地址,再將解析出的 url 地址和對應的 beanName 註冊到父類的 handlerMap 變量中。換句話說,若是你用了 AbstractDetectingUrlHandlerMapping,就不用像 SimpleUrlHandlerMapping 那樣去挨個配置 URL 地址和處理器的映射關係了。咱們來看下 AbstractDetectingUrlHandlerMapping#initApplicationContext 方法:
@Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); detectHandlers(); } protected void detectHandlers() throws BeansException { ApplicationContext applicationContext = obtainApplicationContext(); String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); for (String beanName : beanNames) { String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { registerHandler(urls, beanName); } } }
AbstractDetectingUrlHandlerMapping 重寫了父類的 initApplicationContext 方法,並在該方法中調用了 detectHandlers 方法,在 detectHandlers 中,首先查找到全部的 beanName,而後調用 determineUrlsForHandler 方法分析出 beanName 對應的 URL,不過這裏的 determineUrlsForHandler 方法是一個空方法,具體的實如今它的子類中,AbstractDetectingUrlHandlerMapping 只有一個子類 BeanNameUrlHandlerMapping,咱們一塊兒來看下:
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { @Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<>(); if (beanName.startsWith("/")) { urls.add(beanName); } String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); } }
這個類很簡單,裏邊就一個 determineUrlsForHandler 方法,這個方法的執行邏輯也很簡單,就判斷 beanName 是否是以 /
開始,若是是,則將之做爲 URL。
若是咱們想要在項目中使用 BeanNameUrlHandlerMapping,配置方式以下:
<bean class="org.javaboy.init.HelloController" name="/hello"/> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping"> </bean>
注意,Controller 的 name 必須是以 /
開始,不然該 bean 不會被自動做爲處理器。
至此,AbstractUrlHandlerMapping 體系下的東西就和你們分享完了。
AbstractHandlerMethodMapping 體系下只有三個類,分別是 AbstractHandlerMethodMapping、RequestMappingInfoHandlerMapping 以及 RequestMappingHandlerMapping,以下圖:
在前面第三小節的 AbstractUrlHandlerMapping 體系下,一個 Handler 通常就是一個類,可是在 AbstractHandlerMethodMapping 體系下,一個 Handler 就是一個 Mehtod,這也是咱們目前使用 SpringMVC 時最多見的用法,即直接用 @RequestMapping 去標記一個方法,該方法就是一個 Handler。
接下來咱們就一塊兒來看看 AbstractHandlerMethodMapping。
AbstractHandlerMethodMapping 類實現了 InitializingBean 接口,因此 Spring 容器會自動調用其 afterPropertiesSet 方法,在這裏將完成初始化操做:
@Override public void afterPropertiesSet() { initHandlerMethods(); } protected void initHandlerMethods() { for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods()); } protected String[] getCandidateBeanNames() { return (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); } protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { } if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } }
能夠看到,具體的初始化又是在 initHandlerMethods 方法中完成的,在該方法中,首先調用 getCandidateBeanNames 方法獲取容器中全部的 beanName,而後調用 processCandidateBean 方法對這些候選的 beanName 進行處理,具體的處理思路就是根據 beanName 找到 beanType,而後調用 isHandler 方法判斷該 beanType 是否是一個 Handler,isHandler 是一個空方法,在它的子類 RequestMappingHandlerMapping 中被實現了,該方法主要是檢查該 beanType 上有沒有 @Controller
或者 @RequestMapping
註解,若是有,說明這就是咱們想要的 handler,接下來再調用 detectHandlerMethods 方法保存 URL 和 handler 的映射關係:
protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } }); methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } }
上面這段代碼裏又涉及到兩個方法:
咱們分別來看:
getMappingForMethod
getMappingForMethod 是一個模版方法,具體的實現也是在子類 RequestMappingHandlerMapping 裏邊:
@Override @Nullable protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { info = typeInfo.combine(info); } String prefix = getPathPrefix(handlerType); if (prefix != null) { info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); } } return info; }
首先根據 method 對象,調用 createRequestMappingInfo 方法獲取一個 RequestMappingInfo,一個 RequestMappingInfo 包含了一個接口定義的詳細信息,例如參數、header、produces、consumes、請求方法等等信息都在這裏邊。接下來再根據 handlerType 也獲取一個 RequestMappingInfo,並調用 combine 方法將兩個 RequestMappingInfo 進行合併。接下來調用 getPathPrefix 方法查看 handlerType 上有沒有 URL 前綴,若是有,就添加到 info 裏邊去,最後將 info 返回。
這裏要說一下 handlerType 裏邊的這個前綴是那裏來的,咱們能夠在 Controller 上使用 @RequestMapping
註解,配置一個路徑前綴,這樣 Controller 中的全部方法都加上了該路徑前綴,可是這種方式須要一個一個的配置,若是想一次性配置全部的 Controller 呢?咱們可使用 Spring5.1 中新引入的方法 addPathPrefix 來配置,以下:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setPatternParser(new PathPatternParser()).addPathPrefix("/itboyhub", HandlerTypePredicate.forAnnotation(RestController.class)); } }
上面這個配置表示,全部的 @RestController
標記的類都自動加上 itboyhub
前綴。有了這個配置以後,上面的 getPathPrefix 方法獲取到的就是 /itboyhub
了。
registerHandlerMethod
當找齊了 URL 和 handlerMethod 以後,接下來就是將這些信息保存下來,方式以下:
protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); } public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { HandlerMethod handlerMethod = createHandlerMethod(handler, method); validateMethodMapping(handlerMethod, mapping); Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); for (String path : directPaths) { this.pathLookup.add(path, mapping); } String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { corsConfig.validateAllowCredentials(); this.corsLookup.put(handlerMethod, corsConfig); } this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null)); } finally { this.readWriteLock.writeLock().unlock(); } }
HC#hello
,HC 是 HelloController 中的大寫字母。多說一句,第四步這個東西有啥用呢?這個實際上是 Spring4 中開始增長的功能,算是一個小彩蛋吧,雖然平常開發不多用,可是我這裏仍是和你們說一下。
假如你有以下一個接口:
@RestController @RequestMapping("/javaboy") public class HelloController { @GetMapping("/aaa") public String hello99() { return "aaa"; } }
當你請求該接口的時候,不想經過路徑,想直接經過方法名,行不行呢?固然能夠!
在 jsp 文件中,添加以下超連接:
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <a href="${s:mvcUrl('HC#hello99').build()}">Go!</a> </body> </html>
當這個 jsp 頁面渲染完成後,href 屬性就自動成了 hello99 方法的請求路徑了。這個功能的實現,就依賴於前面第四步的內容。
至此,咱們就把 AbstractHandlerMethodMapping 的初始化流程看完了。
接下來咱們來看下當請求到來後,AbstractHandlerMethodMapping 會如何處理。
和前面第三小節同樣,這裏處理請求的入口方法也是 getHandlerInternal,以下:
@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = initLookupPath(request); this.mappingRegistry.acquireReadLock(); try { HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } if (!matches.isEmpty()) { Match bestMatch = matches.get(0); if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); if (CorsUtils.isPreFlightRequest(request)) { for (Match match : matches) { if (match.hasCorsConfig()) { return PREFLIGHT_AMBIGUOUS_MATCH; } } } else { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.getHandlerMethod().getMethod(); Method m2 = secondBestMatch.getHandlerMethod().getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.getHandlerMethod(); } else { return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); } }
這裏就比較容易,經過 lookupHandlerMethod 找到對應的 HandlerMethod 返回便可,若是 lookupHandlerMethod 方法返回值不爲 null,則經過 createWithResolvedBean 建立 HandlerMethod(主要是確認裏邊的 Bean 等),具體的建立過程鬆哥在後面的文章中會專門和你們分享。lookupHandlerMethod 方法也比較容易:
大體的流程就是這樣,具體到請求並無涉及到它的子類。
SpringMVC 九大組件,今天和小夥伴們把 HandlerMapping 過了一遍,其實只要認真看,這裏並無難點。若是小夥伴們以爲閱讀吃力,也能夠在公衆號後臺回覆 ssm,查看鬆哥錄製的免費入門教程~
剩下的八大組件源碼解析,小夥伴們敬請期待!