前面跟小伙伴們分享了 SpringMVC 一個(gè)大致的初始化流程以及請(qǐng)求的大致處理流程炭序,在請(qǐng)求處理過程中矢否,涉及到九大組件,分別是:
- HandlerMapping
- HandlerAdapter
- HandlerExceptionResolver
- ViewResolver
- RequestToViewNameTranslator
- LocaleResolver
- ThemeResolver
- MultipartResolver
- FlashMapManager
這些組件相信小伙伴們?cè)谌粘i_發(fā)中多多少少都有涉及到陋葡,如果你對(duì)這些組件感到陌生桃笙,可以在公眾號(hào)后臺(tái)回復(fù) ssm,免費(fèi)獲取松哥的入門視頻教程档插。
那么接下來的幾篇文章慢蜓,松哥想和大家深入分析這九大組件,從用法到源碼郭膛,挨個(gè)分析晨抡,今天我們就先來看看這九大組件中的第一個(gè) HandlerMapping。
1.概覽
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
- AbstractUrlHandlerMapping
其他的都是一些輔助接口。
AbstractHandlerMethodMapping 體系下的都是根據(jù)方法名進(jìn)行匹配的谎僻,而 AbstractUrlHandlerMapping 體系下的都是根據(jù) URL 路徑進(jìn)行匹配的娄柳,這兩者有一個(gè)共同的父類 AbstractHandlerMapping,接下來我們就對(duì)這三個(gè)關(guān)鍵類進(jìn)行詳細(xì)分析艘绍。
2.AbstractHandlerMapping
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<Object> interceptors) {
}
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<HandlerInterceptor> 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í)行流程是這樣的:
- 首先調(diào)用 getHandlerInternal 方法去嘗試獲取處理器强重,getHandlerInternal 方法也是一個(gè)模版方法绞呈,該方法將在子類中實(shí)現(xiàn)。
- 如果沒找到相應(yīng)的處理器间景,則調(diào)用 getDefaultHandler 方法獲取默認(rèn)的處理器佃声,我們?cè)谂渲?HandlerMapping 的時(shí)候可以配置默認(rèn)的處理器。
- 如果找到的處理器是一個(gè)字符串倘要,則根據(jù)該字符串找去 SpringMVC 容器中找到對(duì)應(yīng)的 Bean圾亏。
- 確保 lookupPath 存在,一會(huì)找對(duì)應(yīng)的攔截器的時(shí)候會(huì)用到。
- 找到 handler 之后志鹃,接下來再調(diào)用 getHandlerExecutionChain 方法獲取 HandlerExecutionChain 對(duì)象夭问。
- 接下來 if 里邊的是進(jìn)行跨域處理的,獲取到跨域的相關(guān)配置曹铃,然后進(jìn)行驗(yàn)證&配置缰趋,檢查是否允許跨域☆踔唬跨域這塊的配置以及校驗(yàn)還是蠻有意思的埠胖,松哥以后專門寫文章來和小伙伴們細(xì)聊。
接下來我們?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)些侍,接下來我們就來看看它的子類。
3.AbstractUrlHandlerMapping
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;
}
- 首先找到 lookupPath趁猴,就是請(qǐng)求的路徑刊咳。這個(gè)方法本身松哥就不多說了,之前在Spring5 里邊的新玩法儡司!這種 URL 請(qǐng)求讓我漲見識(shí)了芦缰!一文中有過介紹。
- 接下來就是調(diào)用 lookupHandler 方法獲取 Handler 對(duì)象枫慷,lookupHandler 有一個(gè)重載方法,具體用哪個(gè),主要看所使用的 URL 匹配模式或听,如果使用了最新的 PathPattern(Spring5 之后的)探孝,則使用三個(gè)參數(shù)的 lookupHandler;如果還是使用之前舊的 AntPathMatcher誉裆,則這里使用兩個(gè)參數(shù)的 lookupHandler顿颅。
- 如果前面沒有獲取到 handler 實(shí)例,則接下來再做各種嘗試足丢,去分別查找 RootHandler粱腻、DefaultHandler 等,如果找到的 Handler 是一個(gè) String斩跌,則去 Spring 容器中查找該 String 對(duì)應(yīng)的 Bean绍些,再調(diào)用 validateHandler 方法來校驗(yàn)找到的 handler 和 request 是否匹配,不過這是一個(gè)空方法耀鸦,子類也沒有實(shí)現(xiàn)柬批,所以可以忽略之。最后再通過 buildPathExposingHandler 方法給找到的 handler 添加一些參數(shù)袖订。
這就是整個(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<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;
}
- 這里首先調(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 方法添加攔截器塘娶。
- 如果 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ù)處理。
- 接下來處理通配符的情況芽偏。首先定義 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)行匹配。 - 由于一個(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í) |
- 找到 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)證。 - 接下來調(diào)用 extractPathWithinPattern 方法提取出映射路徑姆怪,例如定義的接口規(guī)則是
myroot/*.html
叛赚,請(qǐng)求路徑是myroot/myfile.html
,那么最終獲取到的就是myfile.html
稽揭。 - 接下來的 for 循環(huán)是為了處理存在多個(gè)最佳匹配規(guī)則的情況俺附,在第四步中,我們對(duì) matchingPatterns 進(jìn)行排序溪掀,排序完成后事镣,選擇第一項(xiàng)作為最佳選項(xiàng)賦值給 bestMatch,但是最佳選項(xiàng)可能會(huì)有多個(gè)揪胃,這里就是處理最佳選項(xiàng)有多個(gè)的情況璃哟。
- 最后調(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<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 中添加了兩個(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 的初始化伙单,具體流程如下:
- 如果沒有設(shè)置 lazyInitHandlers获高,并且 handler 是 String 類型,那么就去 Spring 容器中找到對(duì)應(yīng)的 Bean 賦值給 resolvedHandler吻育。
- 根據(jù) urlPath 去 handlerMap 中查看是否已經(jīng)有對(duì)應(yīng)的處理器了念秧,如果有的話,則拋出異常布疼,一個(gè) URL 地址只能對(duì)應(yīng)一個(gè)處理器摊趾,這個(gè)很好理解。
- 接下來根據(jù) URL 路徑游两,將處理器進(jìn)行配置砾层,最終添加到 handlerMap 變量中。
這就是 AbstractUrlHandlerMapping 的主要工作器罐,其中 registerHandler 將在它的子類中調(diào)用梢为。
接下來我們來看 AbstractUrlHandlerMapping 的子類。
3.1 SimpleUrlHandlerMapping
為了方便處理轰坊,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<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);
});
}
}
這塊代碼很簡(jiǎn)單颤芬,實(shí)在沒啥好說的悲幅,如果 URL 不是以 /
開頭,則手動(dòng)給它加上 /
即可站蝠。有小伙伴們可能要問了汰具,urlMap 的值從哪里來?當(dāng)然是從我們的配置文件里邊來呀菱魔,像下面這樣:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/aaa" value-ref="/hello"/>
</map>
</property>
</bean>
3.2 AbstractDetectingUrlHandlerMapping
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<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);
}
}
這個(gè)類很簡(jiǎn)單件蚕,里邊就一個(gè) determineUrlsForHandler 方法孙技,這個(gè)方法的執(zhí)行邏輯也很簡(jiǎn)單,就判斷 beanName 是不是以 /
開始排作,如果是牵啦,則將之作為 URL。
如果我們想要在項(xiàng)目中使用 BeanNameUrlHandlerMapping妄痪,配置方式如下:
<bean class="org.javaboy.init.HelloController" name="/hello"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping">
</bean>
注意哈雏,Controller 的 name 必須是以 /
開始,否則該 bean 不會(huì)被自動(dòng)作為處理器衫生。
至此裳瘪,AbstractUrlHandlerMapping 體系下的東西就和大家分享完了。
4.AbstractHandlerMethodMapping
AbstractHandlerMethodMapping 體系下只有三個(gè)類罪针,分別是 AbstractHandlerMethodMapping彭羹、RequestMappingInfoHandlerMapping 以及 RequestMappingHandlerMapping,如下圖:
在前面第三小節(jié)的 AbstractUrlHandlerMapping 體系下泪酱,一個(gè) Handler 一般就是一個(gè)類派殷,但是在 AbstractHandlerMethodMapping 體系下,一個(gè) Handler 就是一個(gè) Mehtod墓阀,這也是我們目前使用 SpringMVC 時(shí)最常見的用法毡惜,即直接用 @RequestMapping 去標(biāo)記一個(gè)方法,該方法就是一個(gè) Handler斯撮。
接下來我們就一起來看看 AbstractHandlerMethodMapping经伙。
4.1 初始化流程
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<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);
});
}
}
- 首先找到 handler 的類型 handlerType螃征。
- 調(diào)用 ClassUtils.getUserClass 方法檢查是否是 cglib 代理的子對(duì)象類型搪桂,如果是,則返回父類型盯滚,否則將參數(shù)直接返回踢械。
- 接下來調(diào)用 MethodIntrospector.selectMethods 方法獲取當(dāng)前 bean 中所有符合要求的 method。
- 遍歷 methods魄藕,調(diào)用 registerHandlerMethod 方法完成注冊(cè)内列。
上面這段代碼里又涉及到兩個(gè)方法:
- getMappingForMethod
- registerHandlerMethod
我們分別來看:
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<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();
}
}
- 首先調(diào)用 createHandlerMethod 方法創(chuàng)建 HandlerMethod 對(duì)象。
- 調(diào)用 validateMethodMapping 方法對(duì) handlerMethod 進(jìn)行驗(yàn)證操刀,主要是驗(yàn)證 handlerMethod 是否已經(jīng)存在烁挟。
- 從 mappings 中提取出 directPaths,就是不包含通配符的請(qǐng)求路徑骨坑,然后將請(qǐng)求路徑和 mapping 的映射關(guān)系保存到 pathLookup 中撼嗓。
- 找到所有 handler 的簡(jiǎn)稱,調(diào)用 addMappingName 方法添加到 nameLookup 中欢唾。例如我們?cè)?HelloController 中定義了一個(gè)名為 hello 的請(qǐng)求接口静稻,那么這里拿到的就是
HC#hello
,HC 是 HelloController 中的大寫字母匈辱。 - 初始化跨域配置,并添加到 corsLookup 中杀迹。
- 將構(gòu)建好的關(guān)系添加到 registry 中亡脸。
多說一句,第四步這個(gè)東西有啥用呢树酪?這個(gè)其實(shí)是 Spring4 中開始增加的功能浅碾,算是一個(gè)小彩蛋吧,雖然日常開發(fā)很少用续语,但是我這里還是和大家說一下垂谢。
假如你有如下一個(gè)接口:
@RestController
@RequestMapping("/javaboy")
public class HelloController {
@GetMapping("/aaa")
public String hello99() {
return "aaa";
}
}
當(dāng)你請(qǐng)求該接口的時(shí)候,不想通過路徑疮茄,想直接通過方法名滥朱,行不行呢?當(dāng)然可以力试!
在 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>
當(dāng)這個(gè) jsp 頁面渲染完成后,href 屬性就自動(dòng)成了 hello99 方法的請(qǐng)求路徑了畸裳。這個(gè)功能的實(shí)現(xiàn)缰犁,就依賴于前面第四步的內(nèi)容。
至此,我們就把 AbstractHandlerMethodMapping 的初始化流程看完了帅容。
4.2 請(qǐng)求處理
接下來我們來看下當(dāng)請(qǐng)求到來后颇象,AbstractHandlerMethodMapping 會(huì)如何處理。
和前面第三小節(jié)一樣并徘,這里處理請(qǐng)求的入口方法也是 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 找到對(duì)應(yīng)的 HandlerMethod 返回即可饮亏,如果 lookupHandlerMethod 方法返回值不為 null耍贾,則通過 createWithResolvedBean 創(chuàng)建 HandlerMethod(主要是確認(rèn)里邊的 Bean 等),具體的創(chuàng)建過程松哥在后面的文章中會(huì)專門和大家分享路幸。lookupHandlerMethod 方法也比較容易:
- 首先根據(jù) lookupPath 找到匹配條件 directPathMatches荐开,然后將獲取到的匹配條件添加到 matches 中(不包含通配符的請(qǐng)求走這里)简肴。
- 如果 matches 為空温数,說明根據(jù) lookupPath 沒有找到匹配條件,那么直接將所有匹配條件加入 matches 中(包含通配符的請(qǐng)求走這里)讨韭。
- 對(duì) matches 進(jìn)行排序块茁,并選擇排序后的第一個(gè)為最佳匹配項(xiàng),如果前兩個(gè)排序相同辫狼,則拋出異常初斑。
大致的流程就是這樣,具體到請(qǐng)求并沒有涉及到它的子類膨处。
5.小結(jié)
SpringMVC 九大組件见秤,今天和小伙伴們把 HandlerMapping 過了一遍,其實(shí)只要認(rèn)真看真椿,這里并沒有難點(diǎn)鹃答。如果小伙伴們覺得閱讀吃力,也可以在公眾號(hào)后臺(tái)回復(fù) ssm突硝,查看松哥錄制的免費(fèi)入門教程~
剩下的八大組件源碼解析测摔,小伙伴們敬請(qǐng)期待!