Spring的請(qǐng)求映射handlerMapping以及原理詳解
請(qǐng)求映射原理
也就是說(shuō)我們每次發(fā)請(qǐng)求,它到底是怎么找到我們哪個(gè)方法來(lái)去處理這個(gè)請(qǐng)求,因?yàn)槲覀冎浪械恼?qǐng)求過(guò)來(lái)都會(huì)來(lái)到 DispatcherServlet 。
springboot 底層還是使用的是 springMVC 所以 springMVC 的 DispatcherServlet 是處理所以請(qǐng)求的開(kāi)始,他的整個(gè)請(qǐng)求處理方法是,我們來(lái)找一下:
DispatcherServlet 說(shuō)起來(lái)也是一個(gè) servlet 它繼承 FrameworkServlet 又繼承于 HttpServletBean 又繼承于 HttpServlet 。
說(shuō)明
DispatcherServlet 是一個(gè) HttpServlet ,繼承于 Servlet 必須重寫(xiě) doGet 或 doPost 之類(lèi)的方法 Ctril + F12 (打開(kāi) HttpServlet 整個(gè)結(jié)構(gòu))我們發(fā)現(xiàn)這里沒(méi)有 doGet() 或 doSet() 方法,那說(shuō)明子類(lèi)里面有沒(méi)有重寫(xiě)。
它的繼承樹(shù)是:
DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
我們?cè)?Servlet 本來(lái)有 doGet 和 doPost 方法,但是我們發(fā)現(xiàn)在 HttpServletBean 里面沒(méi)有找到,那就在 FrameworkServlet 里面有沒(méi)有, Ctril + F12 看看有沒(méi)有 doGet 和 doPost 。
protected final void doGet() 是繼承了 HttpServletBean 。
在 FrameworkServlet 里 我們發(fā)現(xiàn)無(wú)論是 doGet 還是 doPost 最終都是調(diào)用我們本類(lèi)的 processRequest 說(shuō)明我們請(qǐng)求處理一開(kāi)始我們 HttpServlet 的 doGet 最終會(huì)調(diào)用到我們 FrameworkServlet 里面的 processRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = this.buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
this.initContextHolders(request, localeContext, requestAttributes);
//這些都是初始化過(guò)程
try {
this.doService(request, response);
} catch (IOException | ServletException var16) {
failureCause = var16;
throw var16;
} catch (Throwable var17) {
failureCause = var17;
throw new NestedServletException("Request processing failed", var17);
} finally {
this.resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
this.logResult(request, response, (Throwable)failureCause, asyncManager);
this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
}
}processRequest -> doService()
我們嘗試執(zhí)行一個(gè) doService() 方法,執(zhí)行完后都是一些清理過(guò)程( catch )
protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;
它由于是一個(gè)抽象方法( abstract ),他也沒(méi)有重寫(xiě)和實(shí)現(xiàn),只能來(lái)到子類(lèi)( DispatcherServlet )來(lái)到 DispatcherServlet 來(lái)找 doService() :
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
this.logRequest(request);
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap();
Enumeration attrNames = request.getAttributeNames();
label116:
while(true) {
String attrName;
do {
if (!attrNames.hasMoreElements()) {
break label116;
}
attrName = (String)attrNames.nextElement();
} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
try {
this.doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}也就是說(shuō)最終 DispatcherServlet 里面對(duì) doService() 進(jìn)行了實(shí)現(xiàn)
只要看到 get 、 set 都是在里面放東西進(jìn)行初始化的過(guò)程
我們一連串 請(qǐng)求一進(jìn)來(lái)應(yīng)該是調(diào)HttpServlet的doGet,在FrameworkServlet重寫(xiě)了 唯一有效語(yǔ)句是抽象類(lèi)doService()方法,而這個(gè)方法在DispatcherServlet類(lèi)中實(shí)現(xiàn)
核心的方法調(diào)用
try {
this.doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}叫 doDispatch
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}意思是把我們 doDispatch() 請(qǐng)求做派發(fā),看看 doDispatch() :
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;//初始化數(shù)據(jù)
Object dispatchException = null;//初始化數(shù)據(jù)
try {
processedRequest = this.checkMultipart(request);//檢查我們是否有文件上傳請(qǐng)求
multipartRequestParsed = processedRequest != request;//如果是文件上傳請(qǐng)求它在這進(jìn)行一個(gè)轉(zhuǎn)化
// Determine handler for the current request. 就是我們來(lái)決定哪個(gè)handler(Controller)能處理當(dāng)前請(qǐng)求(current)
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}我們發(fā)現(xiàn)這里才是真正有功能的方法
processedRequest = this.checkMultipart(request); //checkMultipart():檢查文件上傳
doDispatch() 才是我們 DispatcherServlet 里面最終要研究的方法,每一個(gè)請(qǐng)求進(jìn)來(lái)都要調(diào)用 doDispatch 方法
斷點(diǎn)
我們打上斷點(diǎn),來(lái)看整個(gè)請(qǐng)求處理, 包括 它是怎么找到我們每一個(gè)請(qǐng)求要調(diào)用誰(shuí)來(lái)處理的
如果我來(lái)發(fā)送請(qǐng)求(登錄( localhost:8080 ))放行,知道頁(yè)面出來(lái)后我們點(diǎn)擊 REST-GET 請(qǐng)求,我們來(lái)看, protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 傳入原生的 request 和 response ;我們點(diǎn)進(jìn) request 里面我們發(fā)現(xiàn)我們整個(gè)請(qǐng)求的路徑( coyoteRequest )是 /user ,請(qǐng)求的詳細(xì)信息都在這( coyoteRequest ),路徑( decodeUriMB )是 /user 。我們接下開(kāi)看要用誰(shuí)調(diào)用的。
HttpServletRequest processedRequest = request; 相當(dāng)于把原生的請(qǐng)求( request )拿過(guò)來(lái)包裝一下( processedRequest )。
這有個(gè) HandlerExecutionChain mappedHandler = null; 執(zhí)行量我們后來(lái)再說(shuō)
然后繼續(xù),說(shuō) multipartRequestParsed 是不是一個(gè)文件上傳請(qǐng)求,默認(rèn)是 false ,然后包括我們整個(gè)請(qǐng)求期間有沒(méi)有異步( getAsyncManager() ),如果有異步使用異步管理器( WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); )暫時(shí)在這塊我們不用管。
注意 :只有一個(gè) ModelAndView mv = null;Object dispatchException = null; 這些都是空初始化的一些數(shù)據(jù)。加下來(lái)看我們第一個(gè)有功能的一塊:在 doDispatch() 里面第一個(gè)有功能的叫 checkMultipart (檢查我們是否有文件上傳請(qǐng)求,響應(yīng)文件上傳再說(shuō))如果是文件上傳請(qǐng)求它在這進(jìn)行一個(gè)轉(zhuǎn)化( multipartRequestParsed = processedRequest != request; )注意這有一個(gè) // Determine handler for the current request. , 就是我們來(lái)決定哪個(gè)handler能處理當(dāng)前請(qǐng)求(current) 我們放行( mappedHandler = this.getHandler(processedRequest); ) mappedHandler 就會(huì)看到:它直接給我們找到了 HelloCOntroller 的 getUser() 方法來(lái)處理這個(gè)請(qǐng)求
神奇的地方就在這個(gè)(mappedHandler = this.getHandler(processedRequest);) 它到底是怎么找到我當(dāng)前的 /User 請(qǐng)求會(huì)要調(diào)用那個(gè)方法進(jìn)行處理的。
我們從 HttpServletRequest processedRequest = request; 直接放行到 mappedHandler = this.getHandler(processedRequest); , getHandler 他要依據(jù)當(dāng)前請(qǐng)求( processedRequest )當(dāng)前請(qǐng)求里面肯定有哪個(gè) url 地址這是 http 傳過(guò)來(lái)的不用管( Step into )進(jìn)來(lái),這里有一個(gè)東西叫 handlerMappings 也就是獲取到所有的(這個(gè) handlerMappings 有五個(gè))
1、handlerMapping:處理器映射
也就是說(shuō)我們springMVC怎么知道哪個(gè)請(qǐng)求要用誰(shuí)處理,是根據(jù)處理器里面的映射規(guī)則。
也就是說(shuō):
/xxx請(qǐng)求 -> xxx處理 都有這映射規(guī)則,而這些規(guī)則都被保存到 handlerMapping 里面,如上圖所示,我們現(xiàn)在有5個(gè) handlerMapping ,其中有幾個(gè) handlerMapping 大家可能有點(diǎn)熟悉,比如: WelcomePageHandlerMapping (歡迎頁(yè)的處理請(qǐng)求),我們之前說(shuō)資源管理規(guī)則的時(shí)候我們發(fā)現(xiàn)我們springMVC自動(dòng)的會(huì)給容器中放一個(gè)歡迎頁(yè)的 handlerMapping 然后這個(gè) handlerMapping ()里面也有保存規(guī)則,保存什么規(guī)則?就是我們所有的 index 請(qǐng)求你的比如這個(gè) PathMatcher (路徑匹配)我們這個(gè) / (當(dāng)前項(xiàng)目下的’/‘你直接訪問(wèn)這個(gè))我給你訪問(wèn)到哪?我們的’/‘會(huì)直接 rootHandler下的View路徑 (這里是等于到了’index’)所以我們首頁(yè)要訪問(wèn)到的是我們這個(gè) WelcomePageHandlerMapping 里面保存了一個(gè)規(guī)則,所以就有首頁(yè)的訪問(wèn)了。
我們加下來(lái)還有一個(gè) RequestMappingHandlerMapping
2、RequestMappingHandlerMapping
我們以前有一個(gè)注解叫 @RequestMapping 相當(dāng)于是 @RequestMapping 注解的所有處理器映射,也就是說(shuō)這個(gè)東西( RequestMappingHandlerMapping )里面保存了所有 @RequestMapping 和 handler 的規(guī)則。而它又是怎么保存的,那其實(shí)是 我們的應(yīng)用一啟動(dòng)springMVC自動(dòng)掃描我們所有的 Controller 并解析注解,把你的這些注解信息全部保存到HandlerMapping里面 。所以它會(huì)在這五個(gè) handlerMapping 里面(大家注意增強(qiáng)for循環(huán))挨個(gè)找我們所有的請(qǐng)求映射,看誰(shuí)能處理這個(gè)請(qǐng)求,我們找到第一個(gè) handlerMapping 相當(dāng)于我們的 RequestMappingHandlerMapping ,它里面保存了哪些映射信息,有個(gè) mappingRegistry (相當(dāng)于我們映射的注冊(cè)中心)這個(gè)中心里面打開(kāi)你就會(huì)發(fā)現(xiàn)
我們當(dāng)前項(xiàng)目里寫(xiě)的所有的路徑它在這個(gè)都有映射: POST[/user] 是哪個(gè)Controller哪個(gè)方法處理的,包括系統(tǒng)自帶的 /error 它是哪個(gè) Controller 哪個(gè)方法處理的
相當(dāng)于是我們 RequestMappingHandlerMapping 保存了我們當(dāng)前系統(tǒng)我們每一個(gè)自己寫(xiě)的類(lèi),每一個(gè)類(lèi)每一個(gè)方法都能處理什么請(qǐng)求。我們當(dāng)前請(qǐng)求 /user ,我們遍歷到第一個(gè) HandlerMapping 的時(shí)候相當(dāng)于它的注冊(cè)中心( mappingRegistry )里面就能找到 /user 是誰(shuí)來(lái)處理,所以最終在這決定是在 HelloController#getUser 來(lái)處理的。所以它在這( HandlerExecutionChain handler = mapping.getHandler(request); )所以它在 mapping 里面 getHandler ( mapping.getHandler );從我們的 HandlerMapping 里面獲取( getHandler() )我們的 handler 就是處理器,點(diǎn)進(jìn) 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 (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
logger.debug("Mapped to " + executionChain.getHandler());
}
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;
}getHandlerInterna() 我們來(lái)獲取,怎么獲取( step into ):
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
try {
return super.getHandlerInternal(request);
}
finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}( step into ) getHandlerInternal() :
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();
}
}它先拿到 request 原生請(qǐng)求,我們現(xiàn)在想要訪問(wèn)的路徑( lookupPath )我們想要訪問(wèn)的路徑是 /user 然后帶著這個(gè)路徑,它還拿到一把鎖( acquireReadLock() )害怕我們并發(fā)查詢(xún)我們這個(gè) mappingRegistry 這個(gè) mappingRegistry 也看到了是我們這個(gè) RequestMappingHandlerMapping , handlerMapping 里面的一個(gè)屬性 mappingRegistry 它里面保存了我們所有請(qǐng)求調(diào)用哪個(gè)方法處理
所以它最終相當(dāng)于是在我們當(dāng)前請(qǐng)求( request )這個(gè)路徑( lookupPath )到底誰(shuí)來(lái)處理( handlerMethod )
(step into):
@Nullable
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()) {//沒(méi)找到
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);//添加一些空的東西
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);//它把它找到的你里面的第一個(gè)拿過(guò)來(lái)
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
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);
}
}形參:
lookupPath(當(dāng)前我們要找的路徑);request(我們?cè)恼?qǐng)求),接下來(lái)就在下面開(kāi)始找。
從我們這個(gè) Registry ( mappingRegistry )里面,使用我們這個(gè)路徑( getMappingsByDirectPath(lookupPath) )然后去找誰(shuí)能處理;
問(wèn)題就是在于我們這個(gè) Registry ( RequestMappingHandlerMapping )里面他的路徑光靠 /user 請(qǐng)求其實(shí)有四個(gè)人的路徑都是這樣只是請(qǐng)求方式不對(duì)。
getMappingsByDirectPath()
先是根據(jù) url ( getMappingsByDirectPath() 視頻里是 getMappingByUrl() )來(lái)找,按照前面的來(lái)能找到4個(gè)( directPathMatches (+ArraysList@7307 size=4) )( GET 、 POST 、 PUT 、 DELETE 方式的user)找到了以后接下來(lái)它把所有找到的添加到我們這個(gè)匹配的集合里面( addMatchingMappings(directPathMatches, matches, request); )
如果沒(méi)找到( if (matches.isEmpty()) )它就添加一些空的東西( addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); ),如果找到了還不為空( if (!matches.isEmpty()) )接下來(lái)它把它找到的你里面的第一個(gè)拿過(guò)來(lái)( Match bestMatch = matches.get(0); )
如果它同時(shí)找到了很多他就認(rèn)為第一個(gè)是最匹配的,而且大家注意: 如果我們現(xiàn)在的 matches.size() 大于1( if (matches.size() > 1) )也就是相當(dāng)于我們找到了非常多的 matches ;
注意:這個(gè) matches 在這( addMatchingMappings(directPathMatches, matches, request); ),在這一塊( directPathMatches )找到了四個(gè),它( matches )把這四個(gè)調(diào)用 addMatchingMappings() 這個(gè)方法獲取到能匹配的集合里面( matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping))); ); addMatchingMappings() 肯定以請(qǐng)求方式匹配好了
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
}
}
}所以最終給我們留下我們最佳匹配的這個(gè) matches 里面集合里面只有一個(gè),他( bestMatch = matches.get(0); )就拿到這個(gè),所以最終給我們留下一個(gè)我們最佳匹配的我們這個(gè) matches (里面只有一個(gè)),然后它( matches.get(0); )就拿到這個(gè)( matches ),這個(gè) matches ( matches.get(0); )已經(jīng)得到最佳匹配的了,如果你寫(xiě)了多個(gè)方法同時(shí)都能處理 /GET 請(qǐng)求,那你的這個(gè) matches 就能大于1( if (matches.size() > 1) )大于1后(if里面)各種排序排完以后在這( Match secondBestMatch = matches.get(1); )最后給你測(cè)試,把你能匹配的一倆個(gè)全都拿來(lái)進(jìn)行對(duì)比
最終比完后給你抱一個(gè)錯(cuò)說(shuō): throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); (相當(dāng)于我們這個(gè) handler 你能處理這個(gè) uri (路徑) 有倆個(gè)方法都能處理)說(shuō)明就會(huì)拋出異常( IllegalStateException ),所以 springMVC 要求我們:同樣的一個(gè)請(qǐng)求方式不能有多個(gè)方法同時(shí)能處理,只能有一個(gè)。
springMVC 要求我們:同樣的一個(gè)請(qǐng)求方式不能有多個(gè)方法同時(shí)能處理,只能有一個(gè)(原因在上面一段)
最終我們就找到我們最佳匹配規(guī)則(GET方式的user)能匹配,而GET方式的User是 Controller#getUser() 方法
所以簡(jiǎn)單總結(jié)起來(lái)就是一句話:怎么知道哪個(gè)請(qǐng)求誰(shuí)能處理?所有的請(qǐng)求映射都保存在了``HandlerMapping`中。
我們 springboot 一啟動(dòng)給我們配置了 welcomePageHandlerMapping (歡迎頁(yè)的 handlerMapping )
- springboot自動(dòng)配置歡迎頁(yè)的handlerMaping。訪問(wèn)’/'能訪問(wèn)到’index’頁(yè)面
- 請(qǐng)求進(jìn)來(lái),挨個(gè)嘗試所有的HandlerMapping看是否有請(qǐng)求信息。
- 如果有就找個(gè)請(qǐng)求對(duì)象的handler
- 如果沒(méi)有就是下一個(gè)HandlerMapping
總結(jié)
(這里以歡迎頁(yè)為例) localhost:8080/ 進(jìn)入到首頁(yè),我們請(qǐng)求一進(jìn)來(lái)( HttpServletRequest processedRequest = request; )它來(lái)找( mappedHandler = getHandler(processedRequest); (當(dāng)前請(qǐng)求訪問(wèn)的是 / 的請(qǐng)求))看誰(shuí)能處理( getHandler() )接下來(lái)就進(jìn)入了遍歷循環(huán)( forEach )所有 HandlerMapping 的時(shí)候了,我們先來(lái)找到第一個(gè) handlerMapping ( HandlerExecutionChain handler = mapping.getHandler(request); )第一個(gè) HandlerMapping 由于我們這個(gè)里面映射只保存了相當(dāng)于是我們自己 Controller 寫(xiě)的這個(gè)路徑映射沒(méi)人能處理,所以如果在第一個(gè)里面找,你找到的handler肯定是空的,找到是空的,所以繼續(xù)for循環(huán);再次for循環(huán)來(lái)到第二個(gè) handlerMapping 叫 WelcomePageHandlerMapping ,它正好處理的路徑就是’/'所以我們現(xiàn)在找,就找到 handler 了,而這個(gè) handler 是什么?就是我們 springMVC 里面默認(rèn)處理我們’index’頁(yè)面,人家給我們的 ViewController 訪問(wèn)我們的’index’頁(yè)面就有人了。這就是 handlerMapping 的匹配規(guī)則。所有的 handleMapping 全部來(lái)進(jìn)行匹配誰(shuí)能匹配用誰(shuí)的 springboot幫我們配置了哪些HandlerMapping
我們來(lái)到webMvcAutoCOnfig看看有沒(méi)有跟HandlerMapping有關(guān)的:
首先第一個(gè):
- RequestMappingHandlerMapping
- 相當(dāng)于我們?nèi)萜髦?@Bean),我們第一個(gè)HandlerMaping是我們springboot給我們?nèi)萜鞣诺哪J(rèn)的。 這個(gè)組件就是來(lái)解析我們當(dāng)前所有的方法(Controller里的方法)標(biāo)了@RequestMapping注解(GET PUT都一樣)標(biāo)了這些注解的時(shí)候它(RequestMappingHandlerMapping)整的。
- WelcomePageHandlerMapping
- 歡迎頁(yè)的HandlerMapping
- 還有我們系統(tǒng)兼容的BeanNameUrlHandlerMapping、RouterFunctionMapping和SimpleUrlHandlerMapping
- 一句話: 我們需要自定義的映射處理,我們也可以自己在容器中放HandlerMapping(就是來(lái)保存一個(gè)請(qǐng)求誰(shuí)來(lái)處理,甚至于是發(fā)一個(gè)(我們經(jīng)常自定義handlerMappingapi/v1/user和api/v2/user(v2版本獲取用戶)不一樣,v1版本調(diào)用哪個(gè)v2版本調(diào)用哪個(gè))這就可能不止止是Controller的變化,我們希望能自定義handlerMapping的規(guī)則。如果是v1版本,所有的請(qǐng)求,比如去哪個(gè)包里找;如果是v2版本給我去哪個(gè)包里找。這樣就會(huì)非常方便)自定義handlerMapping
到此這篇關(guān)于Spring的請(qǐng)求映射handlerMapping以及原理詳解的文章就介紹到這了,更多相關(guān)Spring的請(qǐng)求映射handlerMapping內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之KMP算法
在很多地方也都經(jīng)??吹街v解KMP算法的文章,看久了好像也知道是怎么一回事,但總感覺(jué)有些地方自己還是沒(méi)有完全懂明白。這兩天花了點(diǎn)時(shí)間總結(jié)一下,有點(diǎn)小體會(huì),我希望可以通過(guò)我自己的語(yǔ)言來(lái)把這個(gè)算法的一些細(xì)節(jié)梳理清楚,也算是考驗(yàn)一下自己有真正理解這個(gè)算法2022-02-02
SpringBoot通過(guò)redisTemplate調(diào)用lua腳本并打印調(diào)試信息到redis log(方法步驟詳解)
這篇文章主要介紹了SpringBoot通過(guò)redisTemplate調(diào)用lua腳本 并打印調(diào)試信息到redis log,本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02
IntelliJ IDEA Java項(xiàng)目手動(dòng)添加依賴(lài) jar 包的方法(圖解)
這篇文章主要介紹了IntelliJ IDEA Java項(xiàng)目手動(dòng)添加依賴(lài) jar 包,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Java中super關(guān)鍵字介紹以及super()的使用
這幾天看到類(lèi)在繼承時(shí)會(huì)用到this和super,這里就做了一點(diǎn)總結(jié),下面這篇文章主要給大家介紹了關(guān)于Java中super關(guān)鍵字介紹以及super()使用的相關(guān)資料,需要的朋友可以參考下2022-01-01
新版idea如何開(kāi)啟多臺(tái)JVM虛擬機(jī)的流程步驟
在IntelliJ?IDEA這個(gè)集成開(kāi)發(fā)環(huán)境中(IDE),開(kāi)啟JVM(Java?Virtual?Machine)通常是在運(yùn)行Java應(yīng)用程序時(shí)的操作,本文給大家介紹了新版idea如何開(kāi)啟多臺(tái)JVM虛擬機(jī)的流程步驟,需要的朋友可以參考下2024-10-10
利用session實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能
這篇文章主要為大家詳細(xì)介紹了利用session實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
通過(guò)Maven下載依賴(lài)Jar包的流程分享
本文主要介紹了Maven下載依賴(lài)Jar包的詳細(xì)流程,包括輸入Maven倉(cāng)庫(kù)的官方地址、搜索依賴(lài)Jar包、選擇版本、復(fù)制和粘貼Maven依賴(lài)到pom文件中下載依賴(lài)Jar包2025-02-02

