您好,登錄后才能下訂單哦!
前言
要深入理解spring mvc的工作流程,就需要先了解spring mvc的架構:
從上圖可以看到 前端控制器DispatcherServlet在其中起著主導作用,理解了DispatcherServlet 就完全可以說弄清楚了spring mvc。
DispatcherServlet作為Spring用于處理web請求注冊的唯一一個Servlet,所有的請求都是經由DispatcherServlet進行分發處理的。本文主要講解DispatcherServlet是如何對請求進行分發,處理,并且生成相應的視圖的。
1. 整體結構
在HttpServlet中,其對不同方式的請求進行了分發,比如對于GET請求,其提供了doGet()方法,對于POST請求,其提供了doPost()方法等等。通過這種方式,子類可以針對于當前請求的方式實現不同的方法即可。但是在DispatcherServlet中,由于需要使用同一的方式對不同的請求進行處理,因而其對各個請求方式進行了整合,如下就是DispatcherServlet針對GET和POST請求所編寫的同一處理邏輯:
@Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); }
可以看到,無論是GET請求還是POST請求,DispatcherServlet都是委托給了processRequest()方法處理,對于其他的請求方式,其處理方式也是類似的。通過這種方式,DispatcherServlet將各個請求整合在了一起,雖然整合在了一起,但是request中也還是保存有當前請求的請求方式的,因而保存了后續對請求進行分發的能力。這里我們直接看processRequest()方法是如何處理各個請求的:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; // 獲取先前請求的LocaleContext LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); // 獲取當前請求的LocaleContext,其中保存了當前請求的Locale信息 LocaleContext localeContext = buildLocaleContext(request); // 獲取先前請求的Attributes信息 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); // 獲取當前請求的Attributes信息,其中保存了當前請求的各個屬性數據 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // 獲取當前請求的WebAsyncManager,這只有在當前請求是請求的異步任務時才會真正用到 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); // 注冊異步任務的攔截器,如果請求的是異步任務,這個攔截器可以攔截異步任務的前置,后置和異常等情況 asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); // 將當前請求的Locale,Attributes信息初始化到對應的ThreadLocal對象中,用于后續使用 initContextHolders(request, localeContext, requestAttributes); try { // 對當前請求進行分發 doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { // 在請求完成之后,判斷當前請求的Locale和Attributes信息是否需要繼承,如果需要繼承, // 則會將Locale信息設置到inheritableLocaleContextHolder中,而將Attributes // 信息設置到inheritableRequestAttributesHolder中;否則就會移除對應的信息, // 而只為當前請求的ContextHolder設置相應的屬性 resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { // 調用已注冊的在當前請求被銷毀時的回調函數,并且更新Session中當前請求所更新的屬性 requestAttributes.requestCompleted(); } if (logger.isDebugEnabled()) { if (failureCause != null) { this.logger.debug("Could not complete request", failureCause); } else { if (asyncManager.isConcurrentHandlingStarted()) { logger.debug("Leaving response open for concurrent processing"); } else { this.logger.debug("Successfully completed request"); } } } // 發布請求已經完成的事件,以便對該事件進行監聽的程序進行相應的處理 publishRequestHandledEvent(request, response, startTime, failureCause); } }
可以看到,processRequest()方法主要是對Locale和Attributes信息進行了處理,然后就通過doService()方法對請求再次進行了分發。我們這里繼續閱讀doService()方法的源碼:
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]"); } // 這里主要是判斷當前請求是否為include請求,如果是include請求,那么就會將當前請求中的 // 數據都放入一個快照中,在當前請求完成之后,會從該塊中中取出數據,然后將其重新加載到 // 當前request中,以便request進行后續的處理。這里默認情況下是會對所有的屬性進行處理的, // 因為cleanupAfterInclude默認值為true,如果將其設置為false,那么就只會對Spring框架 // 相關的屬性進行處理 Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // 這里分別將ApplicationContext,LoacleResolver,ThemeResolver和ThemeSource等 // bean添加到當前request中 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // 這里FlashMapManager主要的作用在于當請求如果是重定向的請求,那么可以將一些屬性保存在FlashMap // 中,然后通過FlashMapManager進行管理,從而在重定向之后能夠獲取到重定向之前所保存的請求 if (this.flashMapManager != null) { // 在當前請求中獲取FlashMap數據,如果不是重定向之后的請求,那么這里獲取到的就是空值 FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { // 將獲取到的FlashMap數據保存在request中 request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } // 設置默認的FlashMap和FlashMapManager request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } try { // 這里才是真正的對請求進行分發處理的位置 doDispatch(request, response); } finally { // 判斷當前請求不是一個異步任務的請求,但是是一個include請求,那么就會重新加載 // 請求之前保存的快照數據 if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
這里的doService()方法也還沒有對請求進行真正的處理,其首先判斷了當前請求是不是一個include請求,如果是include請求,那么就將請求的屬性都保存在一個快照中,以便請求完成之后能夠重新進行加載;然后會判斷當前是否是一個重定向之后的請求,如果是重定向之后的請求,那么其FlashMapManager就不是空的,此時會將重定向之前保存的屬性重新加載到當前請求中;最后doService()方法才會調用doDispatch()方法進行請求的分發和處理。如下是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 { ModelAndView mv = null; Exception dispatchException = null; try { // 這里判斷當前請求是否為一個文件請求,這里的判斷方式就是要求當前請求滿足兩點:①請求 // 方式是POST;②判斷contentType是否以multipart/開頭。如果滿足這兩點,那么就認為當前 // 請求是一個文件請求,此時會將當前請求的request對象封裝為一個 // MultipartHttpServletRequest對象,這也是我們在定義文件請求的Controller時 // 能夠將request參數寫為MultipartHttpServletRequest的原因。這里如果不是文件請求, // 那么會將request直接返回。 processedRequest = checkMultipart(request); // 這里判斷原始request與轉換后的request是否為同一個request,如果不是同一個,則說明 // 其是一個文件請求 multipartRequestParsed = (processedRequest != request); // 這里getHandler()方法就是通過遍歷當前Spring容器中所有定義的HandlerMapping對象, // 通過調用它們的getHandler()方法,看當前的HandlerMapping能否將當前request映射 // 到某個handler,也就是某個Controller方法上,如果能夠映射到,則說明該handler能夠 // 處理當前請求 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // 如果每個HandlerMapping都無法找到與當前request匹配的handler,那么就認為 // 無法處理當前請求,此時一般會返回給頁面404狀態碼 noHandlerFound(processedRequest, response); return; } // 通過找到的handler,然后在當前Spring容器中找到能夠支持將當前request請求適配到 // 找到的handler上的HandlerAdapter。這里需要找到這樣的適配器的原因是,我們的handler // 一般都是Controller的某個方法,其是一個Java方法,而當前request則是一種符合http // 協議的請求,這里是無法直接將request直接應用到handler上的,因而需要使用一個適配器, // 也就是這里的HandlerAdapter。由于前面獲取handler的時候,不同的HandlerMapping // 所產生的handler是不一樣的,比如ReqeustMappingHandlerMapping產生的handler是一個 // HandlerMethod對象,因而這里在判斷某個HandlerAdapter是否能夠用于適配當前handler的 // 時候是通過其supports()方法進行的,比如RequestMappingHandlerAdapter就是判斷 // 當前的handler是否為HandlerMethod類型,從而判斷其是否能夠用于適配當前handler。 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET".equals(method); // 這里判斷請求方式是否為GET或HEAD請求,如果是這兩種請求的一種,那么就會判斷 // 當前請求的資源是否超過了其lastModified時間,如果沒超過,則直接返回, // 并且告知瀏覽器可以直接使用緩存來處理當前請求 if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response) .checkNotModified(lastModified) && isGet) { return; } } // 這里在真正處理請求之前會獲取容器中所有的攔截器,也就是HandlerInterceptor對象, // 然后依次調用其preHandle()方法,如果某個preHandle()方法返回了false,那么就說明 // 當前請求無法通過攔截器的過濾,因而就會直接出發其afterCompletion()方法,只有在 // 所有的preHandle()方法都返回true時才會認為當前請求是能夠使用目標handler進行處理的 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 在當前請求通過了所有攔截器的預處理之后,這里就直接調用HandlerAdapter.handle() // 方法來處理當前請求,并且將處理結果封裝為一個ModelAndView對象。該對象中主要有兩個 // 屬性:view和model,這里的view存儲了后續需要展示的邏輯視圖名或視圖對象,而model // 中則保存了用于渲染視圖所需要的屬性 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 如果當前是一個異步任務,那么就會釋放當前線程,等待異步任務處理完成之后才將 // 任務的處理結果返回到頁面 if (asyncManager.isConcurrentHandlingStarted()) { return; } // 如果返回的ModelAndView對象中沒有指定視圖名或視圖對象,那么就會根據當前請求的url // 來生成一個視圖名 applyDefaultViewName(processedRequest, mv); // 在請求處理完成之后,依次調用攔截器的postHandle()方法,對請求進行后置處理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // 將處理請求過程中產生的異常封裝到dispatchException中 dispatchException = new NestedServletException("Handler dispatch failed", err); } // 這里主要是請求處理之后生成的視圖進行渲染,也包括出現異常之后對異常的處理。 // 渲染完之后會依次調用攔截器的afterCompletion()方法來對請求進行最終處理 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // 如果在上述過程中任意位置拋出異常,包括渲染視圖時拋出異常,那么都會觸發攔截器的 // afterCompletion()方法的調用 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { // 如果當前異步任務已經開始,則觸發異步任務攔截器的afterConcurrentHandlingStarted()方法 if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // 如果當前是一個文件請求,則清理當前request中的文件數據 if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
這里doDispatch()方法是進行請求分發和處理的主干部分,其主要分為如下幾個步驟:
2. handler獲取
從前面的步驟可以看出,請求的具體處理過程主要是通過HandlerMapping根據當前request獲取到對應的handler,然后交由HandlerAdapter將request適配給該handler進行處理,并將處理結果封裝為一個ModelAndView對象,最后將該ModelAndView對象渲染出來。這里我們首先看HandlerMapping根據request查找具體的handler的過程:
@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { // 遍歷當前容器中所有的HandlerMapping對象,調用其getHandler()方法,如果其能夠根據 // 當前request獲取一個handler,那么就直接返回。 for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } } return null; }
這里的邏輯比較簡單,就是遍歷當前容器中所有的HandlerMapping對象,然后依次判斷其是否能夠根據當前request獲取一個handler,如果能夠獲取就直接使用該handler。這里關于HandlerMapping將request映射為handler的過程可以閱讀本人之前的文章:Spring MVC之RequestMappingHandlerMapping匹配。
3. HandlerAdapter獲取與請求處理
在獲取到具體的handler之后,Dispatcher就會根據獲取到的handler查找能夠將當前request適配到該handler的Adapter,這里獲取HandlerAdapter的代碼如下:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { // 遍歷當前容器中所有的HandlerAdapter,通過調用其supports()方法,判斷當前HandlerAdapter // 能否用于適配當前的handler,如果可以,則直接使用該HandlerAdapter for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } } // 如果找不到任何一個HandlerAdapter用于適配當前請求,則拋出異常 throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter" + " that supports this handler"); }
這里獲取HandlerAdapter的過程與HandlerMapping非常的相似,也是遍歷當前容器中所有的HandlerAdapter對象,然后調用其supports()方法,判斷該適配器能否應用于當前handler的適配,如果可以則直接使用該HandlerAdapter。關于HandlerAdapter進行request與handler適配的過程,讀者可閱讀本人之前的文章:Spring MVC之RequestMappingHandlerAdapter詳解。
4. 視圖渲染
在HandlerAdapter進行了請求的適配,并且調用了目標handler之后,其會返回一個ModelAndView對象,該對象中保存有用于渲染視圖的模型數據和需要渲染的視圖名。具體的視圖渲染工作是在processDispatchResult()方法中進行的,這里我們直接閱讀器源碼:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { // 用于標記當前生成view是否是異常處理之后生成的view boolean errorView = false; if (exception != null) { // 如果當前的異常是ModelAndViewDefiningException類型,則說明是ModelAndView的定義 // 異常,那么就會調用其getModelAndView()方法生成一個新的view if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { // 如果生成的異常是其他類型的異常,就會在當前容器中查找能夠處理當前異常的“攔截器”, // 找到之后調用這些攔截器,然后生成一個新的ModelAndView Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // 如果得到的ModelAndView對象(無論是否為異常處理之后生成的ModelAndView)不為空,并且沒有被清理, // 那么就會對其進行渲染,渲染的主要邏輯在render()方法中 if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { // 如果當前是異常處理之后生成的視圖,那么就請求當前request中與異常相關的屬性 WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request " + "handling"); } } // 如果當前正在進行異步請求任務的調用,則直接釋放當前線程,等異步任務處理完之后再進行處理 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return; } // 在視圖渲染完成之后,依次調用當前容器中所有攔截器的afterCompletion()方法 if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
從上面的邏輯可以看出,在進行視圖渲染時,首先會判斷請求處理過程中是否拋出了異常,如果拋出了異常,則會調用相應的異常處理器,獲取異常處理之后的ModelAndView對象,然后通過ModelAndView對象渲染具體的視圖,最后會依次觸發當前容器中所有攔截器的afterCompletion()方法。這里對視圖的具體渲染工作在render()方法中,我們繼續閱讀其源碼:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // 獲取當前請求的Locale信息,該信息在進行視圖的國際化展示時將會非常有用 Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; String viewName = mv.getViewName(); if (viewName != null) { // 如果視圖名不為空,那么就會使用當前容器中配置的ViewResolver根據視圖名獲取一個View對象 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // 如果ModelAndView中沒有視圖名,而提供的View對象,則直接使用該View對象 view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a " + "view name nor a View object in servlet with name '" + getServletName() + "'"); } } if (logger.isDebugEnabled()) { logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); } try { // 設置響應的status屬性 if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // 調用View對象的render()方法來渲染具體的視圖 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "] in DispatcherServlet" + " with name '" + getServletName() + "'", ex); } throw ex; } }
這里的render()方法才是進行視圖渲染的真正方法,首先該方法首先通過ModelAndView對象獲取所要渲染的視圖名,通過ViewResolver生成一個用于視圖渲染的View對象;如果ModelAndView中不是保存的視圖名,而是保存的View對象,則直接使用該對象。在生成View對象之后,通過調用該對象的render()方法渲染得到具體的視圖。這里關于ViewResolver如何獲取到View對象,并且如何進行視圖渲染的過程,讀者可以閱讀本人的文章:Spring MVC之視圖解析。
5. 小結
本文首先從整體上講解了DispatcherServlet是如何對請求進行聚合并且處理的,然后分別從handler獲取,HandlerAdapter進行請求適配,以及視圖的渲染三個方面對請求處理的整體流程進行了講解。這里主要是對DispatcherServlet處理請求的整體流程進行講解,其各個部分的細節讀者可以閱讀本人前面的文章以進行詳細的了解。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。