前言
本文主要是分析一下當一個請求過來的時候,spring mvc如何根據請求資訊查到映射的方法(HandlerMethod)的基本流程。
這裡主要對spring mvc的AbstractHandlerMethodMapping類及其子類的處理流程進行跟蹤,其它的HandlerMapping不作說明。
關于HandlerMethod的注冊及相關類說明,可以檢視spring mvc的RequestMappingHandlerMapping注冊HandlerMethod源碼分析。
整體流程
1. 周遊容器内注冊的所有HandlerMapping執行個體
2. 如果按序周遊時,使用某個HandlerMapping執行個體擷取到Handler不為空,則傳回擷取到結果,後面的HandlerMapping執行個體不再周遊
3. 周遊結束未擷取到比對的Handler,則傳回空
AbstractHandlerMapping的Handler比對
重點分析AbstractHandlerMapping的Handler比對實作,下面進入它的方法源碼,一些代碼我删除了:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
...
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, reque
...
return executionChain;
}
預設Handler(為空)這個就不看了,代碼最下面是在擷取到比對的Handler的時候封裝了攔截器的類似職責鍊的實作,這裡也不關注。是以,接下來隻看下getHandlerInternal這個方法 ,下面的源碼中,大部分不影響邏輯的代碼删除了:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
}
主要邏輯就上面的2行代碼:
1. 擷取到請求路徑lookupPath(預設相對路徑,不是完整的url)
2. 根據lookupPath查找到合适的HandlerMethod
繼續,進入lookupHandlerMethod方法,對着代碼看我加的注釋會清晰點:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
// 根據lookupPath擷取對應的RequestMappingInfo,這個資訊是在注冊的時候已經緩存好了。
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 根據上面擷取到的RequestMappingInfo周遊來比對HandlerMethod。
//這個比對涉及到多個條件:如請求方法、路徑、參數等一系列資訊,過程一兩句話也說不清楚, 但是依然可能會比對到多個
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果根據比對的RequestMappingInfo比對失敗,那就嘗試周遊所有的RequestMappingInfo比對
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
// 可能存在多個,下面的操作會選出一個最合适的HandlerMethod
if (!matches.isEmpty()) {
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);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
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 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
// 如果比對不到了的處理,大多數我們使用的時候,這個場景就認為傳回空吧。
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
如果比對到了,到這裡基本就結束了,也就是根據請求找到了合适的controller。如果沒有比對到就接着周遊剩餘的HandlerMapping繼續比對。
p.s. :本文對比對的主要流程進行了說明,比對的很多細節并沒涉及,因為這個過程中,每一步都能說出很多。