前言:
前面的篇章我們分析了 Spring MVC 工作流程中的 HandlerMapping、HandlerAdapter 的適配過程、攔截器的工作流程,以及處理業務請求的過程,本篇我們分析一下處理完業務解析視圖的方法,也就是 DispatcherServlet#processDispatchResult 方法。
Spring MVC 知識傳送門:
詳解 Spring MVC(Spring MVC 簡介)
Spring MVC 初始化源碼分析
Spring MVC 工作流程源碼分析
Spring MVC 源碼分析之 DispatcherServlet#getHandler 方法
Spring MVC 源碼分析之 DispatcherServlet#getHandlerAdapter 方法
Spring MVC 源碼分析之 AbstractHandlerMethodAdapter#handle 方法
DispatcherServlet#processDispatchResult 方法源碼分析
DispatcherServlet#processDispatchResult 方法名直譯就是初始調度結果,其實就是解析 ModelAndView,源碼也很清晰,先是判斷是否有異常,有異常就解析異常視圖,否則就開始解析 ModelAndView 的流程。
//org.springframework.web.servlet.DispatcherServlet#processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {boolean errorView = false;//是否有異常if (exception != null) {//異常是否是模型視圖異常if (exception instanceof ModelAndViewDefiningException) {this.logger.debug("ModelAndViewDefiningException encountered", exception);//獲取異常視圖mv = ((ModelAndViewDefiningException)exception).getModelAndView();} else {//獲取異常解析器Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;//異常視圖解析mv = this.processHandlerException(request, response, handler, exception);//異常視圖不為空 賦值 errorView 為 trueerrorView = mv != null;}}//模型視圖是否為空 模型視圖是否被標識為清空if (mv != null && !mv.wasCleared()) {//解析并渲染視圖 重點關注this.render(mv, request, response);//errorView if (errorView) {//清除錯誤請求屬性WebUtils.clearErrorRequestAttributes(request);}} else if (this.logger.isTraceEnabled()) {this.logger.trace("No view rendering, null ModelAndView returned.");}//判斷是否是異步處理 if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {//不是異步處理 if (mappedHandler != null) {//注冊后置攔截器mappedHandler.triggerAfterCompletion(request, response, (Exception)null);}}
}
DispatcherServlet#processHandlerException 方法源碼分析
DispatcherServlet#processHandlerException 方法的作用就是解析異常視圖,獲取所有異常解析器,遍歷解析視圖,解析到視圖就停止循環,設置視圖相關屬性返回。
//org.springframework.web.servlet.DispatcherServlet#processHandlerException
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {//刪除請求中的 HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 屬性request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);ModelAndView exMv = null;//異常解析器為空判斷if (this.handlerExceptionResolvers != null) {//迭代遍歷Iterator var6 = this.handlerExceptionResolvers.iterator();while(var6.hasNext()) {HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();//解析 exMv = resolver.resolveException(request, response, handler, ex);//解析到視圖 就跳出循環if (exMv != null) {break;}}}//視圖為 null 判斷if (exMv != null) {//視圖不為空if (exMv.isEmpty()) {//設置異常屬性request.setAttribute(EXCEPTION_ATTRIBUTE, ex);//返回return null;} else {//為空 判斷是否有視圖if (!exMv.hasView()) {//沒有視圖 獲取默認視圖名稱String defaultViewName = this.getDefaultViewName(request);//默認視圖名稱為空判斷if (defaultViewName != null) {//設置視圖名稱exMv.setViewName(defaultViewName);}}if (this.logger.isTraceEnabled()) {this.logger.trace("Using resolved error view: " + exMv, ex);} else if (this.logger.isDebugEnabled()) {this.logger.debug("Using resolved error view: " + exMv);}//設置請求相關的屬性WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());//返回視圖return exMv;}} else {//視圖為空拋出異常throw ex;}
}
DispatcherServlet#render 方法源碼分析
DispatcherServlet#render 方法主要可以分為兩步,分別是創建視圖 View 和 解析渲染視圖 view.render。
//org.springframework.web.servlet.DispatcherServlet#render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {//確認語言環境 常說的國際化Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();//為響應設置 Localeresponse.setLocale(locale);//獲取視圖名稱String viewName = mv.getViewName();View view;//視圖名稱為 null 判斷if (viewName != null) {//解析視圖名稱 重點關注view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);//視圖為空判斷if (view == null) {//視圖為空 拋出異常throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");}} else {//視圖名稱為 null//從模型視圖中獲取 視圖view = mv.getView();//視圖為 null 判斷 if (view == null) {//視圖為 null 拋出異常throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");}}if (this.logger.isTraceEnabled()) {this.logger.trace("Rendering view [" + view + "] ");}try {//模型視圖的狀態判斷if (mv.getStatus() != null) {//不為空 把模型視圖的狀態設置給 responseresponse.setStatus(mv.getStatus().value());}//視圖解析渲染 重點關注view.render(mv.getModelInternal(), request, response);} catch (Exception var8) {if (this.logger.isDebugEnabled()) {this.logger.debug("Error rendering view [" + view + "]", var8);}throw var8;}
}
DispatcherServlet#resolveViewName 方法源碼分析
DispatcherServlet#resolveViewName 方法的主要作用是通過遍歷視圖解析器獲取到視圖,視圖獲取成功則停止遍歷,返回視圖。
//org.springframework.web.servlet.DispatcherServlet#resolveViewName
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {//視圖解析器為空判斷 這里的視圖解析器 就是 DispatcherServlet.properties 中的視圖解析器 InternalResourceViewResolverif (this.viewResolvers != null) {//迭代遍歷視圖解析器Iterator var5 = this.viewResolvers.iterator();while(var5.hasNext()) {//獲取視圖解析器ViewResolver viewResolver = (ViewResolver)var5.next();//通過視圖名稱和語言環境 得到視圖 重點關注View view = viewResolver.resolveViewName(viewName, locale);//為null判斷if (view != null) {//返回視圖return view;}}}return null;
}
AbstractCachingViewResolver#resolveViewName 方法源碼分析
AbstractCachingViewResolver#resolveViewName 方法主要就是創建視圖,它會先判斷是否允許使用緩存,然后去創建視圖或者說從緩存中去獲取視圖,如果允許使用緩存,最終會把創建好的視圖加入到緩存中,我們重點關注 createView 即可。
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {//是否允許緩存if (!this.isCache()) {//不允許 直接根據 viewName 和語言環境 創建一個 View 重點關注return this.createView(viewName, locale);} else {//允許緩存//根據 viewName 和語言環境 從緩存中獲取 viewObject cacheKey = this.getCacheKey(viewName, locale);View view = (View)this.viewAccessCache.get(cacheKey);//view 為 null 判斷if (view == null) {//緩存沒有獲取到 同步鎖 保證線程安全synchronized(this.viewCreationCache) {//再次去 viewCreationCache 緩存中獲取view = (View)this.viewCreationCache.get(cacheKey);//view 為空判斷if (view == null) {//還是為空 就創建一個 view 重點關注view = this.createView(viewName, locale);//為空判斷 cacheUnresolved 默認為 trueif (view == null && this.cacheUnresolved) {//賦值為空視圖 是一個沒有任何實現的視圖view = UNRESOLVED_VIEW;}//view 不為空 加入緩存if (view != null && this.cacheFilter.filter(view, viewName, locale)) {this.viewAccessCache.put(cacheKey, view);this.viewCreationCache.put(cacheKey, view);}}}} else if (this.logger.isTraceEnabled()) {this.logger.trace(formatKey(cacheKey) + "served from cache");}return view != UNRESOLVED_VIEW ? view : null;}
}
UrlBasedViewResolver#createView 方法源碼分析
UrlBasedViewResolver#createView 方法會判斷當前請求的類型,看是請求轉發、重定向、普通請求的哪一種,不同請求類型會有不同的創建 View 的方法。
protected View createView(String viewName, Locale locale) throws Exception {//UrlBasedViewResolver 是否可以處理if (!this.canHandle(viewName, locale)) {return null;} else {//轉發 urlString forwardUrl;//視圖名稱是否是否 redirect: 打頭if (viewName.startsWith("redirect:")) {//截取掉 redirect: 獲取真正的 urlforwardUrl = viewName.substring("redirect:".length());//創建一個重定向視圖RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());//獲取重定向 hostString[] hosts = this.getRedirectHosts();//為 null 判斷if (hosts != null) {//給視圖設置 view view.setHosts(hosts);}//應用給生命周期方法 重點關注return this.applyLifecycleMethods("redirect:", view);} else if (viewName.startsWith("forward:")) {//請求轉發 獲取轉發 urlforwardUrl = viewName.substring("forward:".length());//創建 InternalResourceViewInternalResourceView view = new InternalResourceView(forwardUrl);//應用給生命周期方法 其實就是使用 ApplicationContext 完成 view 的初始化 重點關注return this.applyLifecycleMethods("forward:", view);} else {//不是 redirect 也不是 forward 就是普通視圖 這里會調用 重點關注return super.createView(viewName, locale);}}
}
UrlBasedViewResolver#applyLifecycleMethods 方法源碼分析
UrlBasedViewResolver#applyLifecycleMethods 應用給生命周期方法,其實就是使用 ApplicationContext 完成 View 的初始化 。
//org.springframework.web.servlet.view.UrlBasedViewResolver#applyLifecycleMethods
protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {//獲取 ApplicationContextApplicationContext context = this.getApplicationContext();//為空 判斷if (context != null) {//獲取 beanFactory 完成 view 初始化Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);//初始化完了 是否是 view 類型if (initialized instanceof View) {//返回 viewreturn (View)initialized;}}return view;
}
AbstractCachingViewResolver#createView 方法源碼分析
AbstractCachingViewResolver#createView 方法是普通請求創建 View 的方法,它調用的是父類 AbstractCachingViewResolver 的 createView 方法 ,該方法并沒有什么實際操作,接著調用了 loadView 方法,loadView 方法完成了 View 的創建及初始化、屬性見擦汗等。
//org.springframework.web.servlet.view.AbstractCachingViewResolver#createView
@Nullable
protected View createView(String viewName, Locale locale) throws Exception {return this.loadView(viewName, locale);
}//org.springframework.web.servlet.view.UrlBasedViewResolver#loadView
protected View loadView(String viewName, Locale locale) throws Exception {//根據viewName 構建view 重點關注AbstractUrlBasedView view = this.buildView(viewName);//完成view 初始化View result = this.applyLifecycleMethods(viewName, view);//view.checkResource(locale) 檢查view屬性 默認返回 truereturn view.checkResource(locale) ? result : null;
}
AbstractCachingViewResolver#buildView 方法源碼分析
AbstractCachingViewResolver#buildView 方法獲取 View 的 Class 類型,反射創建了 View,并給 View 設置了各種屬性,是正真創建 View 的方法。
//org.springframework.web.servlet.view.UrlBasedViewResolver#buildView
protected AbstractUrlBasedView buildView(String viewName) throws Exception {//獲取view 的classClass<?> viewClass = this.getViewClass();//斷言判斷 class 是否為 nullAssert.state(viewClass != null, "No view class");//根據 view 的 class 創建一個 viewAbstractUrlBasedView view = (AbstractUrlBasedView)BeanUtils.instantiateClass(viewClass);//根據前綴+名稱+后綴 拼接成視圖信息view.setUrl(this.getPrefix() + viewName + this.getSuffix());//view 設置 Attributeview.setAttributesMap(this.getAttributesMap());//獲取 contentTypeString contentType = this.getContentType();if (contentType != null) {//view 設置 contentTypeview.setContentType(contentType);}//獲取 RequestContextAttributeString requestContextAttribute = this.getRequestContextAttribute();if (requestContextAttribute != null) {//設置 RequestContextAttribute view.setRequestContextAttribute(requestContextAttribute);}//是否暴露 PathVariables Request請求中的 url 屬性Boolean exposePathVariables = this.getExposePathVariables();if (exposePathVariables != null) {//是否將這些屬性暴露在視圖中view.setExposePathVariables(exposePathVariables);}//是否將 bean 暴露在視圖中Boolean exposeContextBeansAsAttributes = this.getExposeContextBeansAsAttributes();if (exposeContextBeansAsAttributes != null) {//設置到視圖中view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);}//需要暴露的 bean nameString[] exposedContextBeanNames = this.getExposedContextBeanNames();if (exposedContextBeanNames != null) {//設置到視圖中view.setExposedContextBeanNames(exposedContextBeanNames);}//返回 視圖return view;
}
AbstractView#render 方法源碼分析
上文說了 DispatcherServlet#render 方法主要可以分為兩步,分別是創建視圖 View 和 解析渲染視圖 view.render,創建 View 的步驟我們已經分析完了,我們來分析一下 view.render 方法,也就是 AbstractView#render 方法,該方法會將 Model、request、response 封裝成成一個 Map 對象給后面的視圖渲染使用,同時會對下載請求做一些處理,然后就開始進行視圖渲染。
//org.springframework.web.servlet.view.AbstractView#render
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {if (this.logger.isDebugEnabled()) {this.logger.debug("View " + this.formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));}//合并成一個 Map 對象Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);//響應前處理 其實就是判斷這是否是一個下載請求 默認不是下載請求this.prepareResponse(request, response);//渲染合并數據模型 重點關注this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
}
InternalResourceView#renderMergedOutputModel 方法源碼分析
InternalResourceView#renderMergedOutputModel 方法就是進行視圖渲染了,對于不同的請求類型有不同的渲染方式。
//org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {//將模型的數據全部寫去到 request 中 就是一個 request.setAttribute 的過程this.exposeModelAsRequestAttributes(model, request);//暴露一個助手 默認空實現 可以理解為一個擴展點this.exposeHelpers(request);//確定請求的路徑String dispatcherPath = this.prepareForRendering(request, response);//獲取可以用于 include、forward 的 RequestDispatcherRequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);//為 null 判斷if (rd == null) {//為 null 拋出異常throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl() + "]: Check that the corresponding file exists within your web application archive!");} else {//判斷當前是否為include請求//include方法使原先的 Servlet 和轉發到的 Servlet 都可以輸出響應信息 即原先的 Servlet 還可以繼續輸出響應信息if (this.useInclude(request, response)) {//是 include 請求//設置 ContentTyperesponse.setContentType(this.getContentType());if (this.logger.isDebugEnabled()) {this.logger.debug("Including [" + this.getUrl() + "]");}//include 包含的意思// 調用 include()方法進行文件引入rd.include(request, response);} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Forwarding to [" + this.getUrl() + "]");}//請求轉發 直接使用 forward 請求將當前請求轉發到目標文件路徑中 渲染該視圖rd.forward(request, response);}}
}
至此,視圖渲染部分的核心流程已經分析完畢,其實整個流程就兩個要點,一個是通過視圖解析器去創建 View,一個就是 View 的渲染過程,視圖渲染分析完畢,也代表著整個 Spring MVC 的工作流程分析完畢,源碼告訴了我們 Spirng MVC 是怎樣去處理一個請求的,希望可以幫助到有需要的小伙伴。
歡迎提出建議及對錯誤的地方指出糾正。