DispatcherServlet執行流程及相關原始碼分析
DispatcherServlet執行流程及相關原始碼分析
在前一篇文章SpringMVC 啟動流程及相關原始碼分析中,詳細探討了Spring MVC
在Web容器
中部署後的啟動過程,以及相關原始碼分析,同時也討論了DispatcherServlet類
的初始化建立過程,相關內容在此不再贅述,如有需求可查閱。
本文主要講解DispatcherServlet類
獲取使用者請求到響應的全過程,並針對相關原始碼進行分析。對於基本的MVC架構
本文不再進行講解,有需要的讀者可自行查閱。
首先,讓我們站在Spring MVC
的四大元件:DispatcherServlet
、HandlerMapping
HandlerAdapter
以及ViewResolver
的角度來看一下Spring MVC
對使用者請求的處理過程,有如下時序圖:
DispatcherServlet執行序列圖
具體處理過程如下:
- 1、使用者請求傳送至
DispatcherServlet類
進行處理。 - 2、
DispatcherServlet類
遍歷所有配置的HandlerMapping類
請求查詢Handler
。 - 3、
HandlerMapping類
根據request請求
的URL
等資訊查詢能夠進行處理的Handler
,以及相關攔截器interceptor
並構造HandlerExecutionChain
- 4、
HandlerMapping類
將構造的HandlerExecutionChain類
的物件返回給前端控制器DispatcherServlet類
。 - 5、前端控制器拿著上一步的
Handler
遍歷所有配置的HandlerAdapter類
請求執行Handler
。 - 6、
HandlerAdapter類
執行相關Handler
並獲取ModelAndView類
的物件。 - 7、
HandlerAdapter類
將上一步Handler
執行結果的ModelAndView 類
的物件返回給前端控制器。 - 8、
DispatcherServlet類
遍歷所有配置的ViewResolver類
請求進行檢視解析。 - 9、
ViewResolver類
進行檢視解析並獲取View
物件。 - 10、
ViewResolver類
向前端控制器返回上一步驟的View
物件。 - 11、
DispatcherServlet類
進行檢視View
的渲染,填充Model
。 - 12、
DispatcherServlet類
向用戶返回響應。
通過時序圖和上面的講解不難發現,整個Spring MVC
對於使用者請求的響應和處理都是以DispatcherServlet類
為核心,其他三大元件均與前端控制器進行互動,三大元件之間沒有互動並且互相解耦,因此,三大元件可以替換不同的實現而互相沒有任何影響,提高了整個架構的穩定性並且降低了耦合度。接下來會按照上述的響應過程逐一進行講解。
DispatcherServlet類
本質上依舊是一個Servlet
並且其父類實現了Servlet介面
,我們知道,Servlet
執行Service()
方法對使用者請求進行響應,根據前一篇文章的分析方法可以得到人如下的呼叫邏輯圖:
service方法呼叫邏輯
從上圖的原始碼呼叫邏輯可以看出,HttpServlet抽象類
實現了Servlet介面
的service(ServletRequest, ServletResponse)
的方法,因此,使用者請求的第一執行方法為該方法,該方法緊接著直接呼叫了service(HttpServletRequest, HttpServletResponse)
方法,其子類FrameworkServlet抽象類
重寫了該方法,因為多型的特性最終是呼叫了FrameworkServlet抽象類
的service(HttpServletRequest, HttpServletResponse)
方法,FrameworkServlet抽象類
同樣也重寫了doHead()
、doPost()
、doPut()
、doDelete()
、doOptions()
、doTrace()
方法,service(ServletRequest, ServletResponse)
方法根據請求型別的不同分別呼叫上述方法,上述六個方法都呼叫了processRequest()
方法,而該方法最終呼叫了DispatcherServlet類
的doService()
方法。通過層層分析,我們找到了最終要呼叫的處理使用者請求的方法,doService()
之前的方法呼叫都比較簡單,這裡不再逐一來檢視原始碼,有興趣的讀者可以自行查閱。
檢視doService()
的原始碼如下:
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@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) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
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));
}
}
}
// Make framework objects available to handlers and view objects.
/*
將當前Servlet的子IoC容器放入request請求中
由此,我們可以訪問到當前IoC子容器以及根IoC容器中的Bean
*/
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());
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);
try {
//真正進行使用者請求的處理
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doService()
方法主要進行一些引數的設定,並將部分引數放入request
請求中,真正執行使用者請求並作出響應的方法則為doDispatch()
方法,檢視doDispatch()
方法的原始碼如下:
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//使用者的request請求
HttpServletRequest processedRequest = request;
//HandlerExecutionChain區域性變數
HandlerExecutionChain mappedHandler = null;
//判斷是否解析了檔案型別的資料,如果有最終需要清理
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
//ModelAndView區域性變數
ModelAndView mv = null;
//處理異常區域性變數
Exception dispatchException = null;
try {
//檢查是否包含檔案等型別的資料
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//向HandlerMapping請求查詢HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
//如果HandlerExecutionChain為null,則沒有能夠進行處理的Handler,丟擲異常
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//根據查詢到的Handler請求查詢能夠進行處理的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
//判斷自上次請求後是否有修改,沒有修改直接返回響應
String method = request.getMethod();
boolean isGet = "GET".equals(method);
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方法
如果任一HandlerInterceptor的preHandle方法沒有通過則不繼續進行處理
*/
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//通過HandlerAdapter執行查詢到的handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//逆序執行HandlerInterceptor的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//渲染檢視填充Model,如果有異常渲染異常頁面
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//如果有異常按倒序執行所有HandlerInterceptor的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//如果有異常按倒序執行所有HandlerInterceptor的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
//倒序執行所有HandlerInterceptor的afterCompletion方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
//如果請求包含檔案型別的資料則進行相關清理工作
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
根據上述原始碼並結合文章開始講解的DispatcherServlet類
結合三大元件對使用者請求的處理過程不難理解相關處理流程。
doDispatch()
方法通過呼叫getHandler()
方法並傳入reuqest
通過HandlerMapping
查詢HandlerExecutionChain
,檢視其原始碼如下:
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
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;
}
getHandler()
方法遍歷了開發者配置的所有HandlerMapping類
根據request
請求來查詢HandlerExecutionChain
,從這裡可以看出,Spring MVC
是支援使用者配置多個HandlerMapping類
的,在處理使用者請求時會逐一查詢,找到後立即返回,因此,如果多個HandlerMapping類
都能夠處理同一request
請求,只會返回第一個能夠處理的HandlerMapping類
構造的HandlerExecutionChain
,所以在配置HandlerMapping類
時需要注意不要對同一請求多次進行處理,由於篇幅問題HandlerMapping類
如何具體查詢Handler
並構造HandlerExecutionChain
的細節不在此進行講解,如有興趣可以查閱本系列文章的第三篇SpringMVC HandlerMapping原始碼分析。
如果沒有找到對應的HandlerExecutionChain
物件,則會執行noHandlerFound()
方法,繼續檢視其原始碼如下:
/**
* No handler found -> set appropriate HTTP response status.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception if preparing the response failed
*/
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
如果沒有找到對應的HandlerExecutionChain
則會丟擲異常NoHandlerFoundException
,在開發的過程中,如果我們將具體的URL
寫錯了則會遇到這個404錯誤。
繼續檢視doDispatch()
方法的原始碼,如果找到了HandlerExecutionChain
接下來會呼叫getHandlerAdapter()
方法來查詢能夠對Handler
進行處理的HandlerAdapter
,檢視其原始碼如下:
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
與HandlerMapping
類似,查詢能夠處理具體Handler
的HandlerAdapter
時同樣會遍歷所有配置了的HandlerAdapter
,HandlerAdapter
是一個介面包含一個support()
方法,該方法根據Handler
是否實現某個特定的介面來判斷該HandlerAdapter
是否能夠處理這個具體的Handler
,這裡使用介面卡模式,通過這樣的方式就可以支援不同型別的HandlerAdapter
。如果沒有查詢到能夠處理Handler
的HandlerAdapter
則會丟擲異常,如果在開發的過程中Handler
在實現介面時出現了問題就可能會遇到上述異常。
查詢到了對應的HandlerAdapter
後就會呼叫HandlerExecutionChain
的applyPreHandle()
方法來執行配置的所有HandlerInteceptor
的preHandle()
方法,檢視其原始碼如下:
/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
HandlerExecutionChain
的applyPreHandle()
方法會按照順序依次呼叫HandlerInterceptor
的preHandle()
方法,但當任一HandlerInterceptor
的preHandle()
方法返回了false
就不再繼續執行其他HandlerInterceptor
的preHandle()
方法,而是直接跳轉執行triggerAfterCompletion()
方法,檢視該方法原始碼如下:
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
這裡遍歷的下標為interceptorIndex
,該變數在前一個方法applyPreHandle()
方法中賦值,如果preHandle()
方法返回true
該變數加一,因此該方法會逆序執行所有preHandle()
方法返回了true
的HandlerInterceptor
的afterCompletion()
方法。到這裡讀者已經掌握了HandlerInterceptor
的preHandle()
方法以及afterCompletion()
方法的執行順序,這些內容並不需要我們死記,需要知道其執行順序檢視原始碼是最好的方法。
繼續閱讀doDispatch()
方法的原始碼,如果所有攔截器的preHandle()
方法都返回了true
沒有進行攔截,接下來前端控制器會請求執行上文獲取的Handler
,這個Handler
就是開發的時候編寫的Controller
,根據實現介面的不同執行相關方法,並獲取到ModelAndView類
的物件。
接下來會執行HandlerInterceptor
的postHandle()
方法,具體原始碼如下:
/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
可以發現,postHandle()
方法是按照逆序執行。
執行完postHandle()
方法後,doDispatch()
方法呼叫了processDispatchResult()
方法,其原始碼如下:
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
//判斷HandlerMapping、HandlerAdapter處理時的異常是否為空
if (exception != null) {
//上述兩個元件處理時的異常不為空
//如果為ModelAndViewDefiningException異常,則獲取一個異常檢視
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
//如果不為ModelAndViewDefiningException異常,進行異常檢視的獲取
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
//判斷mv是否為空,不管是正常的ModelAndView還是異常的ModelAndView,只要存在mv就進行檢視渲染
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
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()) {
// Concurrent handling started during a forward
return;
}
//執行相關HandlerInterceptor的afterCompletion()方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
該方法傳入了一個異常類的物件dispatchException
,閱讀doDispatch()
方法的原始碼可以看出,Spring MVC
對整個doDispatch()
方法用了巢狀的try-catch
語句,內層的try-catch
用於捕獲HandlerMapping
進行對映查詢HandlerExecutionChain
以及HandlerAdapter
執行具體Handler
時的處理異常,並將異常傳入到上述processDispatchResult()
方法中。
processDispatchResult()
方法主要用於針對產生的異常來構造異常檢視,接著不管檢視是正常檢視還是異常檢視均呼叫render()
方法來渲染,檢視render()
方法的具體原始碼如下:
/**
* Render the given ModelAndView.
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
// 解析檢視名稱獲取對應檢視View
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
//如果檢視View為空丟擲異常
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
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() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
//設定Http響應狀態字
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
//呼叫檢視View的render方法通過Model來渲染檢視
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()
方法通過呼叫resolveViewName()
方法根據檢視名稱解析對應的檢視View
,該方法原始碼如下:
/**
* Resolve the given view name into a View object (to be rendered).
* <p>The default implementations asks all ViewResolvers of this dispatcher.
* Can be overridden for custom resolution strategies, potentially based on
* specific model attributes or request parameters.
* @param viewName the name of the view to resolve
* @param model the model to be passed to the view
* @param locale the current locale
* @param request current HTTP servlet request
* @return the View object, or {@code null} if none found
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
* @see ViewResolver#resolveViewName
*/
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
resolveViewName()
方法通過遍歷配置的所有ViewResolver類
根據檢視名稱來解析對應的檢視View
,如果找到則返回對應檢視View
,沒有找到則返回null
。
回到前一個render()
方法,如果上述方法返回的檢視為null
則丟擲異常,這個異常相信大多數人也見過,當開發時寫錯了返回的View
檢視名稱時就會丟擲該異常。接下來呼叫具體檢視的render()
方法來進行Model
資料的渲染填充,最終構造成完整的檢視。
到這裡,doDispatch()
的外層try-catch
異常的作用我們就知道了,為了捕獲渲染檢視時的異常,通過兩層巢狀的try-catch
,Spring MVC
就能夠捕獲到三大元件在處理使用者請求時的異常,通過這樣的方法能夠很方便的實現統一的異常處理。
總結
通過前文的原始碼分析,我們能夠清楚的認識到Spring MVC
對使用者請求的處理過程,進一步加深對Spring MVC
的理解。