掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
前面跟小伙伴們分享了 SpringMVC 一個(gè)大致的初始化流程以及請(qǐng)求的大致處理流程,在請(qǐng)求處理過程中,涉及到九大組件,分別是:

成都創(chuàng)新互聯(lián)從2013年成立,先為大祥等服務(wù)建站,大祥等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為大祥企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
這些組件相信小伙伴們?cè)谌粘i_發(fā)中多多少少都有涉及到,如果你對(duì)這些組件感到陌生,可以在公眾號(hào)后臺(tái)回復(fù) ssm,免費(fèi)獲取松哥的入門視頻教程。
那么接下來的幾篇文章,松哥想和大家深入分析這九大組件,從用法到源碼,挨個(gè)分析,今天我們就先來看看這九大組件中的第一個(gè) HandlerMapping。
HandlerMapping 叫做處理器映射器,它的作用就是根據(jù)當(dāng)前 request 找到對(duì)應(yīng)的 Handler 和 Interceptor,然后封裝成一個(gè) HandlerExecutionChain 對(duì)象返回,我們來看下 HandlerMapping 接口:
- 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;
- }
可以看到,除了一堆聲明的常量外,其實(shí)就一個(gè)需要實(shí)現(xiàn)的方法 getHandler,該方法的返回值就是我們所了解到的 HandlerExecutionChain。
HandlerMapping 的繼承關(guān)系如下:
這個(gè)繼承關(guān)系雖然看著有點(diǎn)繞,其實(shí)仔細(xì)觀察就兩大類:
其他的都是一些輔助接口。
AbstractHandlerMethodMapping 體系下的都是根據(jù)方法名進(jìn)行匹配的,而 AbstractUrlHandlerMapping 體系下的都是根據(jù) URL 路徑進(jìn)行匹配的,這兩者有一個(gè)共同的父類 AbstractHandlerMapping,接下來我們就對(duì)這三個(gè)關(guān)鍵類進(jìn)行詳細(xì)分析。
AbstractHandlerMapping 實(shí)現(xiàn)了 HandlerMapping 接口,無論是通過 URL 進(jìn)行匹配還是通過方法名進(jìn)行匹配,都是通過繼承 AbstractHandlerMapping 來實(shí)現(xiàn)的,所以 AbstractHandlerMapping 所做的事情其實(shí)就是一些公共的事情,將以一些需要具體處理的事情則交給子類去處理,這其實(shí)就是典型的模版方法模式。
AbstractHandlerMapping 間接繼承自 ApplicationObjectSupport,并重寫了 initApplicationContext 方法(其實(shí)該方法也是一個(gè)模版方法),這也是 AbstractHandlerMapping 的初始化入口方法,我們一起來看下:
- @Override
- protected void initApplicationContext() throws BeansException {
- extendInterceptors(this.interceptors);
- detectMappedInterceptors(this.adaptedInterceptors);
- initInterceptors();
- }
三個(gè)方法都和攔截器有關(guān)。
extendInterceptors
- protected void extendInterceptors(List
- }
extendInterceptors 是一個(gè)模版方法,可以在子類中實(shí)現(xiàn),子類實(shí)現(xiàn)了該方法之后,可以對(duì)攔截器進(jìn)行添加、刪除或者修改,不過在 SpringMVC 的具體實(shí)現(xiàn)中,其實(shí)這個(gè)方法并沒有在子類中進(jìn)行實(shí)現(xiàn)。
detectMappedInterceptors
- protected void detectMappedInterceptors(List
mappedInterceptors) { - mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
- obtainApplicationContext(), MappedInterceptor.class, true, false).values());
- }
detectMappedInterceptors 方法會(huì)從 SpringMVC 容器以及 Spring 容器中查找所有 MappedInterceptor 類型的 Bean,查找到之后添加到 mappedInterceptors 屬性中(其實(shí)就是全局的 adaptedInterceptors 屬性)。一般來說,我們定義好一個(gè)攔截器之后,還要在 XML 文件中配置該攔截器,攔截器以及各種配置信息,最終就會(huì)被封裝成一個(gè) MappedInterceptor 對(duì)象。
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 方法主要是進(jìn)行攔截器的初始化操作,具體內(nèi)容是將 interceptors 集合中的攔截器添加到 adaptedInterceptors 集合中。
至此,我們看到,所有攔截器最終都會(huì)被存入 adaptedInterceptors 變量中。
AbstractHandlerMapping 的初始化其實(shí)也就是攔截器的初始化過程。
為什么 AbstractHandlerMapping 中對(duì)攔截器如此重視呢?其實(shí)不是重視,大家想想,AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping 最大的區(qū)別在于查找處理器的區(qū)別,一旦處理器找到了,再去找攔截器,但是攔截器都是統(tǒng)一的,并沒有什么明顯區(qū)別,所以攔截器就統(tǒng)一在 AbstractHandlerMapping 中進(jìn)行處理,而不會(huì)去 AbstractUrlHandlerMapping 或者 AbstractHandlerMethodMapping 中處理。
接下來我們?cè)賮砜纯?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;
- }
這個(gè)方法的執(zhí)行流程是這樣的:
接下來我們?cè)賮砜纯吹谖宀降?getHandlerExecutionChain 方法的執(zhí)行邏輯,正是在這個(gè)方法里邊把 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;
- }
這里直接根據(jù)已有的 handler 創(chuàng)建一個(gè)新的 HandlerExecutionChain 對(duì)象,然后遍歷 adaptedInterceptors 集合,該集合里存放的都是攔截器,如果攔截器的類型是 MappedInterceptor,則調(diào)用 matches 方法去匹配一下,看一下是否是攔截當(dāng)前請(qǐng)求的攔截器,如果是,則調(diào)用 chain.addInterceptor 方法加入到 HandlerExecutionChain 對(duì)象中;如果就是一個(gè)普通攔截器,則直接加入到 HandlerExecutionChain 對(duì)象中。
這就是 AbstractHandlerMapping#getHandler 方法的大致邏輯,可以看到,這里留了一個(gè)模版方法 getHandlerInternal 在子類中實(shí)現(xiàn),接下來我們就來看看它的子類。
AbstractUrlHandlerMapping,看名字就知道,都是按照 URL 地址來進(jìn)行匹配的,它的原理就是將 URL 地址與對(duì)應(yīng)的 Handler 保存在同一個(gè) Map 中,當(dāng)調(diào)用 getHandlerInternal 方法時(shí),就根據(jù)請(qǐng)求的 URL 去 Map 中找到對(duì)應(yīng)的 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;
- }
這就是整個(gè) getHandlerInternal 方法的邏輯,實(shí)際上并不難,里邊主要涉及到 lookupHandler 和 buildPathExposingHandler 兩個(gè)方法,需要和大家詳細(xì)介紹下,我們分別來看。
lookupHandler
lookupHandler 有兩個(gè),我們分別來看。
- @Nullable
- protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
- Object handler = getDirectMatch(lookupPath, request);
- if (handler != null) {
- return handler;
- }
- // Pattern match?
- List
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
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
uriTemplateVariables = new LinkedHashMap<>(); - for (String matchingPattern : matchingPatterns) {
- if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
- Map
vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); - Map
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;
- }
1.這里首先調(diào)用 getDirectMatch 方法直接去 handlerMap 中找對(duì)應(yīng)的處理器,handlerMap 中就保存了請(qǐng)求 URL 和處理器的映射關(guān)系,具體的查找過程就是先去 handlerMap 中找,找到了,如果是 String,則去 Spring 容器中找對(duì)應(yīng)的 Bean,然后調(diào)用 validateHandler 方法去驗(yàn)證(實(shí)際上沒有驗(yàn)證,前面已經(jīng)說了),最后調(diào)用 buildPathExposingHandler 方法添加攔截器。
2.如果 getDirectMatch 方法返回值不為 null,則直接將查找到的 handler 返回,方法到此為止。那么什么情況下 getDirectMatch 方法的返回值不為 null 呢?簡(jiǎn)單來收就是沒有使用通配符的情況下,請(qǐng)求地址中沒有通配符,一個(gè)請(qǐng)求地址對(duì)應(yīng)一個(gè)處理器,只有這種情況,getDirectMatch 方法返回值才不為 null,因?yàn)?handlerMap 中保存的是代碼的定義,比如我們定義代碼的時(shí)候,某個(gè)處理器的訪問路徑可能帶有通配符,但是當(dāng)我們真正發(fā)起請(qǐng)求的時(shí)候,請(qǐng)求路徑里是沒有通配符的,這個(gè)時(shí)候再去 handlerMap 中就找不對(duì)對(duì)應(yīng)的處理器了。如果用到了定義接口時(shí)用到了通配符,則需要在下面的代碼中繼續(xù)處理。
3.接下來處理通配符的情況。首先定義 matchingPatterns 集合,將當(dāng)前請(qǐng)求路徑和 handlerMap 集合中保存的請(qǐng)求路徑規(guī)則進(jìn)行對(duì)比,凡是能匹配上的規(guī)則都直接存入 matchingPatterns 集合中。具體處理中,還有一個(gè) useTrailingSlashMatch 的可能,有的小伙伴 SpringMVC 用的不熟練,看到這里可能就懵了,這里是這樣的,SpringMVC 中,默認(rèn)是可以匹配結(jié)尾 / 的,舉個(gè)簡(jiǎn)單例子,如果你定義的接口是/user,那么請(qǐng)求路徑可以是 /user 也可以 /user/,這兩種默認(rèn)都是支持的,所以這里的 useTrailingSlashMatch 分支主要是處理后面這種情況,處理方式很簡(jiǎn)單,就在 registeredPattern 后面加上 / 然后繼續(xù)和請(qǐng)求路徑進(jìn)行匹配。
4.由于一個(gè)請(qǐng)求 URL 可能會(huì)和定義的多個(gè)接口匹配上,所以 matchingPatterns 變量是一個(gè)數(shù)組,接下來就要對(duì) matchingPatterns 進(jìn)行排序,排序完成后,選擇排序后的第一項(xiàng)作為最佳選項(xiàng)賦值給 bestMatch 變量。默認(rèn)的排序規(guī)則是 AntPatternComparator,當(dāng)然開發(fā)者也可以自定義。AntPatternComparator 中定義的優(yōu)先級(jí)如下:
| 路由配置 | 優(yōu)先級(jí) |
|---|---|
不含任何特殊符號(hào)的路徑,如:配置路由/a/b/c |
第一優(yōu)先級(jí) |
帶有{}的路徑,如:/a//c |
第二優(yōu)先級(jí) |
帶有正則的路徑,如:/a/{regex:\d{3}}/c |
第三優(yōu)先級(jí) |
帶有*的路徑,如:/a/b/* |
第四優(yōu)先級(jí) |
帶有**的路徑,如:/a/b/** |
第五優(yōu)先級(jí) |
最模糊的匹配:/** |
最低優(yōu)先級(jí) |
5.找到 bestMatch 之后,接下來再根據(jù) bestMatch 去 handlerMap 中找到對(duì)應(yīng)的處理器,直接找如果沒找到,就去檢查 bestMatch 是否以 / 結(jié)尾,如果是以 / 結(jié)尾,則去掉結(jié)尾的 / 再去 handlerMap 中查找,如果還沒找到,那就該拋異常出來了。如果找到的 handler 是 String 類型的,則再去 Spring 容器中查找對(duì)應(yīng)的 Bean,接下來再調(diào)用 validateHandler 方法進(jìn)行驗(yàn)證。
6.接下來調(diào)用 extractPathWithinPattern 方法提取出映射路徑,例如定義的接口規(guī)則是 myroot/*.html,請(qǐng)求路徑是 myroot/myfile.html,那么最終獲取到的就是myfile.html。
7.接下來的 for 循環(huán)是為了處理存在多個(gè)最佳匹配規(guī)則的情況,在第四步中,我們對(duì) matchingPatterns 進(jìn)行排序,排序完成后,選擇第一項(xiàng)作為最佳選項(xiàng)賦值給 bestMatch,但是最佳選項(xiàng)可能會(huì)有多個(gè),這里就是處理最佳選項(xiàng)有多個(gè)的情況。
8.最后調(diào)用 buildPathExposingHandler 方法注冊(cè)兩個(gè)內(nèi)部攔截器,該方法下文我會(huì)給大家詳細(xì)介紹。
lookupHandler 還有一個(gè)重載方法,不過只要大家把這個(gè)方法的執(zhí)行流程搞清楚了,重載方法其實(shí)很好理解,這里松哥就不再贅述了,唯一要說的就是重載方法用了 PathPattern 去匹配 URL 路徑,而這個(gè)方法用了 AntPathMatcher 去匹配 URL 路徑。
buildPathExposingHandler
- protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
- String pathWithinMapping, @Nullable Map
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 中添加了兩個(gè)攔截器 PathExposingHandlerInterceptor 和 UriTemplateVariablesHandlerInterceptor,這兩個(gè)攔截器在各自的 preHandle 中分別向 request 對(duì)象添加了一些屬性,具體添加的屬性小伙伴們可以自行查看,這個(gè)比較簡(jiǎn)單,我就不多說了。
在前面的方法中,涉及到一個(gè)重要的變量 handlerMap,我們定義的接口和處理器之間的關(guān)系都保存在這個(gè)變量中,那么這個(gè)變量是怎么初始化的呢?這就涉及到 AbstractUrlHandlerMapping 中的另一個(gè)方法 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) 方法有兩個(gè)參數(shù),第一個(gè)就是定義的請(qǐng)求路徑,第二個(gè)參數(shù)則是處理器 Bean 的名字,第一個(gè)參數(shù)是一個(gè)數(shù)組,那是因?yàn)橥粋€(gè)處理器可以對(duì)應(yīng)多個(gè)不同的請(qǐng)求路徑。
在重載方法 registerHandler(String,String) 里邊,完成了 handlerMap 的初始化,具體流程如下:
這就是 AbstractUrlHandlerMapping 的主要工作,其中 registerHandler 將在它的子類中調(diào)用。
接下來我們來看 AbstractUrlHandlerMapping 的子類。
為了方便處理,SimpleUrlHandlerMapping 中自己定義了一個(gè) urlMap 變量,這樣可以在注冊(cè)之前做一些預(yù)處理,例如確保所有的 URL 都是以 / 開始。SimpleUrlHandlerMapping 在定義時(shí)重寫了父類的 initApplicationContext 方法,并在該方法中調(diào)用了 registerHandlers,在 registerHandlers 中又調(diào)用了父類的 registerHandler 方法完成了 handlerMap 的初始化操作:
- @Override
- public void initApplicationContext() throws BeansException {
- super.initApplicationContext();
- registerHandlers(this.urlMap);
- }
- protected void registerHandlers(Map
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);
- });
- }
- }
這塊代碼很簡(jiǎn)單,實(shí)在沒啥好說的,如果 URL 不是以 / 開頭,則手動(dòng)給它加上/ 即可。有小伙伴們可能要問了,urlMap 的值從哪里來?當(dāng)然是從我們的配置文件里邊來呀,像下面這樣:
AbstractDetectingUrlHandlerMapping 也是 AbstractUrlHandlerMapping 的子類,但是它和 SimpleUrlHandlerMapping 有一些不一樣的地方。
不一樣的是哪里呢?
AbstractDetectingUrlHandlerMapping 會(huì)自動(dòng)查找到 SpringMVC 容器以及 Spring 容器中的所有 beanName,然后根據(jù) beanName 解析出對(duì)應(yīng)的 URL 地址,再將解析出的 url 地址和對(duì)應(yīng)的 beanName 注冊(cè)到父類的 handlerMap 變量中。換句話說,如果你用了 AbstractDetectingUrlHandlerMapping,就不用像 SimpleUrlHandlerMapping 那樣去挨個(gè)配置 URL 地址和處理器的映射關(guān)系了。我們來看下 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 方法,并在該方法中調(diào)用了 detectHandlers 方法,在 detectHandlers 中,首先查找到所有的 beanName,然后調(diào)用 determineUrlsForHandler 方法分析出 beanName 對(duì)應(yīng)的 URL,不過這里的 determineUrlsForHandler 方法是一個(gè)空方法,具體的實(shí)現(xiàn)在它的子類中,AbstractDetectingUrlHandlerMapping 只有一個(gè)子類 BeanNameUrlHandlerMapping,我們一起來看下:
- public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
- @Override
- protected String[] determineUrlsForHandler(String beanName) {
- List
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);
- }
- }
這個(gè)類很簡(jiǎn)單,里邊就一個(gè) determineUrlsForHandler 方法,這個(gè)方法的執(zhí)行邏輯也很簡(jiǎn)單,就判斷 beanName 是不是以 / 開始,如果是,則將之作為 URL。
如果我們想要在項(xiàng)目中使用 BeanNameUrlHandlerMapping,配置方式如下:
注意,Controller 的 name 必須是以 / 開始,否則該 bean 不會(huì)被自動(dòng)作為處理器。
至此,AbstractUrlHandlerMapping 體系下的東西就和大家分享完了。
AbstractHandlerMethodMapping 體系下只有三個(gè)類,分別是 AbstractHandlerMethodMapping、RequestMappingInfoHandlerMapping 以及 RequestMappingHandlerMapping,如下圖:
在前面第三小節(jié)的 AbstractUrlHandlerMapping 體系下,一個(gè) Handler 一般就是一個(gè)類,但是在 AbstractHandlerMethodMapping 體系下,一個(gè) Handler 就是一個(gè) Mehtod,這也是我們目前使用 SpringMVC 時(shí)最常見的用法,即直接用 @RequestMapping 去標(biāo)記一個(gè)方法,該方法就是一個(gè) Handler。
接下來我們就一起來看看 AbstractHandlerMethodMapping。
AbstractHandlerMethodMapping 類實(shí)現(xiàn)了 InitializingBean 接口,所以 Spring 容器會(huì)自動(dòng)調(diào)用其 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 方法中完成的,在該方法中,首先調(diào)用 getCandidateBeanNames 方法獲取容器中所有的 beanName,然后調(diào)用 processCandidateBean 方法對(duì)這些候選的 beanName 進(jìn)行處理,具體的處理思路就是根據(jù) beanName 找到 beanType,然后調(diào)用 isHandler 方法判斷該 beanType 是不是一個(gè) Handler,isHandler 是一個(gè)空方法,在它的子類 RequestMappingHandlerMapping 中被實(shí)現(xiàn)了,該方法主要是檢查該 beanType 上有沒有 @Controller 或者 @RequestMapping 注解,如果有,說明這就是我們想要的 handler,接下來再調(diào)用 detectHandlerMethods 方法保存 URL 和 handler 的映射關(guān)系:
- 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
methods = MethodIntrospector.selectMethods(userType, - (MethodIntrospector.MetadataLookup
) 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);
- });
- }
- }
上面這段代碼里又涉及到兩個(gè)方法:
我們分別來看:
getMappingForMethod
getMappingForMethod 是一個(gè)模版方法,具體的實(shí)現(xiàn)也是在子類 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;
- }
首先根據(jù) method 對(duì)象,調(diào)用 createRequestMappingInfo 方法獲取一個(gè) RequestMappingInfo,一個(gè) RequestMappingInfo 包含了一個(gè)接口定義的詳細(xì)信息,例如參數(shù)、header、produces、consumes、請(qǐng)求方法等等信息都在這里邊。接下來再根據(jù) handlerType 也獲取一個(gè) RequestMappingInfo,并調(diào)用 combine 方法將兩個(gè) RequestMappingInfo 進(jìn)行合并。接下來調(diào)用 getPathPrefix 方法查看 handlerType 上有沒有 URL 前綴,如果有,就添加到 info 里邊去,最后將 info 返回。
這里要說一下 handlerType 里邊的這個(gè)前綴是那里來的,我們可以在 Controller 上使用 @RequestMapping 注解,配置一個(gè)路徑前綴,這樣 Controller 中的所有方法都加上了該路徑前綴,但是這種方式需要一個(gè)一個(gè)的配置,如果想一次性配置所有的 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));
- }
- }
上面這個(gè)配置表示,所有的 @RestController 標(biāo)記的類都自動(dòng)加上 itboyhub前綴。有了這個(gè)配置之后,上面的 getPathPrefix 方法獲取到的就是/itboyhub 了。
registerHandlerMethod
當(dāng)找齊了 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
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.v
分享文章:SpringMVC九大組件之HandlerMapping深入分析
網(wǎng)頁地址:http://uogjgqi.cn/article/dhihgoj.html

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流