1. 程式人生 > >第五章 SpringMVC之ViewResolver和View解析

第五章 SpringMVC之ViewResolver和View解析

          過完年了,本來是想在年前將SpringMVC系列寫完的,只是在接近年末的時候沒有了一種學習心態,這兩天看了一下ViewResolver原始碼,就想盡快將這篇部落格寫出,也好完結SpringMVC的系列部落格並開始下面的學習。

          自己寫的配置檔案springController.xml中和ViewResolve有關的部分
         

        <!--檢視解析器-->  
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
            <!-- webroot到一指定資料夾檔案路徑 -->  
            <property name="prefix" value="/"/>  
            <!-- 檢視名稱字尾  -->  
            <property name="suffix" value=".jsp"/>  
        </bean>
         自己寫的處理器處理使用者的請求,在處理完請求後返回一個ModelAndView物件
package com.wangbiao.springMVC;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

public class HelloWorld extends  MultiActionController{

	public ModelAndView sayHelloWorld(HttpServletRequest request, HttpServletResponse response) {
		String param = request.getParameter("param");
		System.out.println("springMVC測試:helloWorld;"+param);
		ModelAndView mv = new ModelAndView();
		mv.addObject("content", "springMVC HelloWorld:"+param);
		mv.setViewName("springMVC/helloWorld");
		ServletContext ctx = this.getServletContext();	
		return mv;
	}

}
可以看出處理器返回MdoelAndView物件並向此物件中設一個viewName,ViewName是邏輯檢視名,還向MdoelAndView中傳入了數值,其實就是向MdoelAndView傳入了一個Map物件,這個Map物件就是我們常說的資料模型Modle。

一.ViewResolve和View的作用

     1. ViewResolve的作用就是通過解析MdoelAndView,將MdoelAndView中的邏輯檢視名變為一個真正的View物件,並將MdoelAndView中的Model取出。

      2.View的作用就是在獲取到ViewResolve傳來的View和Model,對Model進行渲染,通過View物件找到要展示給使用者的物理檢視,將渲染後的檢視展示給使用者。用很直白的話將就是將資料通過request儲存起來,找到要展示給使用者的頁面,將這些資料放在頁面中,並將頁面呈現給使用者。

二.ViewResolve原始碼介紹

     ViewResolve和前面的HandlerMapping,HandlerAdapter一樣,首先是在啟動服務的時候,IOC容器會根據配置檔案裡面的ViewResolve相關資訊對ViewResolve進行例項化,並存儲到DispatcherServlet的 List<ViewResolver> viewResolvers屬性中。當要解析MdoelAndView物件的時候,會遍歷viewResolvers,從中取出一個viewResolver對進行解析,要是解析出View物件,就不再進行遍歷,要是解析出的View物件是空的,接著從viewResolvers中取出viewResolver對MdoelAndView物件進行解析。

下面是DispatcherServlet類裡面取出IOC容器裡面的viewResolver相關物件,可以看成是DispatcherServlet對viewResolver的註冊。

private void initViewResolvers(ApplicationContext context) {
		this.viewResolvers = null;

		if (this.detectAllViewResolvers) {
			// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
			Map<String, ViewResolver> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
				// We keep ViewResolvers in sorted order.
				OrderComparator.sort(this.viewResolvers);
			}
		}
		else {
			try {
				ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);//從IOC容器裡面的viewResolver相關物件並存儲
				this.viewResolvers = Collections.singletonList(vr);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default ViewResolver later.
			}
		}

		// Ensure we have at least one ViewResolver, by registering
		// a default ViewResolver if no other resolvers are found.
		if (this.viewResolvers == null) {
			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
			}
		}
	}
再看看viewResolver拿到了ModelAndView物件後是怎麼處理的

DispatcherServlet的doDispatch方法中有一個render(mv, processedRequest, response);render就是對ModelAndView物件進行解析

DispatcherServlet的render方法

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()) {
			// 獲取<span style="font-size: 13.3333339691162px;">ModelAndView物件中的檢視名和資料模型,通過邏輯檢視名獲取到真正的view物件。</span>
			view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
			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() + "'");
		}
		view.render(mv.getModelInternal(), request, response);//View物件對Model進行渲染後將檢視展示給使用者
	}
在render方法中主要看兩段程式碼:
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
view.render(mv.getModelInternal(), request, response);
先看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方法中我們看到了通過遍歷viewResolers,從中選出合適的ViewResolver對檢視名進行解析,如果解析出的View不為空,就直接返回了,結合上面的配置檔案,viewResolers中只有一個InternalResourceViewResolver物件。
我們再看ViewResoler的resolveViewName方法。InternalResourceViewResolver的祖先類AbstractCachingViewResolver中有resolveViewName方法。

AbstractCachingViewResolver是實現了ViewResolver介面的抽象方法

AbstractCachingViewResolver中的resolveViewName方法,該方法首先會判斷有沒有快取,要是有快取,它會先去快取中通過viewName查詢是否有View物件的存在,要是沒有,它會通過viewName建立一個新的View物件,並將View物件存入快取中,這樣再次遇到同樣的檢視名的時候就可以直接在快取中取出View物件了

public View resolveViewName(String viewName, Locale locale) throws Exception {
		if (!isCache()) {
			return createView(viewName, locale);
		}
		else {
			Object cacheKey = getCacheKey(viewName, locale);
			synchronized (this.viewCache) {
				View view = this.viewCache.get(cacheKey);
				if (view == null) {
					// Ask the subclass to create the View object.
					view = createView(viewName, locale);
					this.viewCache.put(cacheKey, view);
					if (logger.isTraceEnabled()) {
						logger.trace("Cached view [" + cacheKey + "]");
					}
				}
				return view;
			}
		}
	}

AbstractCachingViewResolver中的createView方法
	protected View createView(String viewName, Locale locale) throws Exception {
		return loadView(viewName, locale);
	}
AbstractCachingViewResolver中的loadView方法是一個抽象方法,它通過AbstractCachingViewResolver的子類UrlBasedViewResolver方法實現

UrlBasedViewResolver中的loadView方法

	protected View loadView(String viewName, Locale locale) throws Exception {
		AbstractUrlBasedView view = buildView(viewName);
		View result = (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
		return (view.checkResource(locale) ? result : null);
	}
UrlBasedViewResolver中的buildView方法

我覺得buildView方法是建立View物件最核心的方法,buildView方法會獲取一個View物件,這個物件會將檢視以什麼格式呈現給使用者,例如如果是jsp顯示呈現給使用者的話,那這個view物件就是JstlView,預設的是JstlView。在這個方法中我們看到了getPrefix() + viewName + getSuffix()這樣一段程式碼,這就是對檢視路徑的一個拼接了,getPrefix()方法獲取字首,也就是我們在配置檔案中配置的 <property name="prefix" value="/"/>  的value中的值了,getSuffix()方法就是獲取字尾值了,也就是我們在配置檔案中配置的<property name="suffix" value=".jsp"/>   的value中的值。這樣就將將檢視的物理路徑找到了,並賦值到View的URL屬性中去。

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
		view.setUrl(getPrefix() + viewName + getSuffix());
		String contentType = getContentType();
		if (contentType != null) {
			view.setContentType(contentType);
		}
		view.setRequestContextAttribute(getRequestContextAttribute());
		view.setAttributesMap(getAttributesMap());
		return view;
	}
就這樣我們得到了一個View物件,這個檢視的name就是邏輯檢視名,因為當將View物件放在快取的時候,我們可以通過邏輯檢視名在快取中找出View物件。我們在獲取到View物件的時候,我們還要將View進行渲染,並呈現給使用者。我們再來看看View中的render方法。

view.render(mv.getModelInternal(), request, response);
View是個介面,View由AbstractView實現。AbstractView中的reder方法
	public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isTraceEnabled()) {
			logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
				" and static attributes " + this.staticAttributes);
		}

		// Consolidate static and dynamic model attributes.
		Map<String, Object> mergedModel =
				new HashMap<String, Object>(this.staticAttributes.size() + (model != null ? model.size() : 0));
		mergedModel.putAll(this.staticAttributes);
		if (model != null) {
			mergedModel.putAll(model);
		}

		// Expose RequestContext?
		if (this.requestContextAttribute != null) {
			mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
		}

		prepareResponse(request, response);
		renderMergedOutputModel(mergedModel, request, response);
	}

renderMergedOutputModel方法由AbstractView的孫子類InternalResourceView實現

InternalResourceView的renderMergedOutputModel方法

renderMergedOutputModel方法中我們看到了我們剛學servlet的一絲痕跡。我們獲取到檢視的物理路徑,然後將這段路徑傳給RequestDispatcher物件,再呼叫RequestDispatcher的forward方法將頁面呈現給使用者,這樣就走完了檢視的解析了。至於RequestDispatcher的forward方法是如何根據檢視路徑將頁面呈現給使用者,這個我也不知道,只是知道這個方法是這麼用的罷了。

	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Determine which request handle to expose to the RequestDispatcher.
		HttpServletRequest requestToExpose = getRequestToExpose(request);

		// Expose the model object as request attributes.
		exposeModelAsRequestAttributes(model, requestToExpose);

		// Expose helpers as request attributes, if any.
		exposeHelpers(requestToExpose);

		// Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(requestToExpose, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
		if (rd == null) {
			throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
					"]: Check that the corresponding file exists within your web application archive!");
		}

		// If already included or response already committed, perform include, else forward.
		if (useInclude(requestToExpose, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.include(requestToExpose, response);
		}

		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			exposeForwardRequestAttributes(requestToExpose);
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.forward(requestToExpose, response);
		}
	}
renderMergedOutputModel方法中呼叫了InternalResourceView祖先類AbstractView的exposeModelAsRequestAttributes方法

AbstractViewexposeModelAsRequestAttributes方法,在exposeModelAsRequestAttributes方法中是不是看到了我非常熟悉的一段程式碼

request.setAttribute(modelName, modelValue);
就是將ModelAndView中的Mdoel裡面的值交給request儲存,我們就可以在頁面就可以通過el表示式來獲取這些值了。
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
		for (Map.Entry<String, Object> entry : model.entrySet()) {
			String modelName = entry.getKey();
			Object modelValue = entry.getValue();
			if (modelValue != null) {
				request.setAttribute(modelName, modelValue);
				if (logger.isDebugEnabled()) {
					logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
							"] to request in view with name '" + getBeanName() + "'");
				}
			}
			else {
				request.removeAttribute(modelName);
				if (logger.isDebugEnabled()) {
					logger.debug("Removed model object '" + modelName +
							"' from request in view with name '" + getBeanName() + "'");
				}
			}
		}
	}

    已經快晚上十一點啦,我還在公司寫部落格,哈哈,是不是很認真,哎,一整天了還沒將這片部落格寫完,慚愧啊。該回去啦,明天來進行一下總結。

     三.ResolverView和View相關類的介紹

      1.類AbstractCachingViewResolver

       AbstractCachingViewResolver實現了ResolverView介面。AbstractCachingViewResolver類的主要作用就是在快取中通過邏輯檢視名查詢檢視,如果沒有查詢到,就去建立一個新的檢視,並將該檢視存入快取中。

      2.類UrlBasedViewResolver

       UrlBasedViewResolver繼承了AbstractCachingViewResolver。UrlBasedViewResolver的主要作用是建立一個View的物件,這個View的物件可以在配置檔案中配置,也可以取預設的,預設的就是JstlView,讀取配置檔案中對ResolverView的配置,根據邏輯檢視名找到真正檢視的路徑,將路徑存入View物件中。這樣就得到了一個View物件。

      3.類AbstractView

       AbstractView實現了View介面。AbstractView的主要作用是渲染檢視和將model中的資料取出來並傳給頁面。AbstractView渲染檢視只是實現了一個抽象方法,該功能主要靠AbstractView的孫子類InternalResourceView來實現

      4.AbstractUrlBasedView類

      主要是起到一個承上啟下的作用,其他的作用這個我也不知道。

      5.InternalResourceView類

       InternalResourceView繼承了AbstractUrlBasedView,InternalResourceView的主要作用就是拿到檢視的路徑,建立一個RequestDispatcher物件。將檢視路徑給RequestDispatcher,RequestDispatcher條用forward方法,將檢視展示給使用者。

      有很多細節的地方其實自己也不是特別懂,所以就沒有寫。總結的話等以後自己對ResolverView和View後再來寫。


相關推薦

SpringMVCViewResolverView解析

          過完年了,本來是想在年前將SpringMVC系列寫完的,只是在接近年末的時候沒有了一種學習心態,這兩天看了一下ViewResolver原始碼,就想盡快將這篇部落格寫出,也好完結SpringMVC的系列部落格並開始下面的學習。           自己寫的

組合語言之第五章知識彙總 組合語言之第五章【BX】loop指令 組合語言之第包含多個段的程式 組合語言之第更靈活的定位記憶體地址的方法 彙編實驗之第資料處理的兩個基本問題

      組合語言之第五章【BX】和loop指令   一:【bx】   【bx】和之前用過的【0】有些類似,都是表示記憶體單元,而它的偏移地址在bx中。段地址預設在ds中   描述一個記憶體單元 需要知道,1記憶體單元的地址,

linux重定向管道

控制 windows 空白 設備 example 替換 刪除fstab num 研究 重定向和管道 標準輸入和輸出: 程序:指令+數據 讀入數據:Input 輸出數據:Output 打開的文件都有一個fd: file descriptor (文件描述符) Linux給程序提

用戶組管理權限

用戶 組 權限管理 筆記整理開始2018年4月3日17:27:07 本章內容: 解釋Linux的安全模型 解釋用戶賬號和組群賬號的目的 用戶的組管理命令 理解並設置文件權限 默認權限 特殊權限 ACL

CLR via C#學習筆記--引用類型值類型

引用類型 tro 生成 形式 編譯 就是 type 完全匹配 成員 5.2 引用類型和值類型 使用引用類型 CLR支持兩種類型:引用類型和值類型。雖然FCL的大多數類型都是引用類型,但程序員用的最多的還是值類型。 引用類型總是從托管堆分配,C#的new操作符返回對象內存地址

《Java 解惑》 異常

簡述: 《Java 解惑》 第五章 異常之謎 - 筆記 內容: 謎題36: try中的return不會影響finally中方法執行 package 異常之謎.優柔寡斷; public class Indecisive { public static vo

SpringMVC_(攔截器過濾器)

1:什麼是過濾器(Filter) 過濾器Filter:過濾器通過實現Filter介面,實現了過濾器的三個方法,分別是初始化方法,dofilter方法和銷燬方法,隨著容器的啟動和銷燬而初始化和銷燬,依賴於servlet容器,過濾器攔截的是位址列請求,過濾器實在進入容器後執行的servlet之前後執

演算法導論 :概率分析隨機演算法 筆記(僱傭問題、指示器隨機變數、隨機演算法、概率分析指示器隨機變數的進一步使用)

僱傭問題: 假設你需要僱用一名新的辦公室助理。你先前的僱傭嘗試都以失敗告終,所以你決定找一個僱用代理。僱用代理每天給你推薦一個應聘者。你會面試這個人,然後決定要不要僱用他。你必須付給僱用代理一小筆費用來面試應聘者。要真正地僱用一個應聘者則要花更多的錢,因為你必須辭掉目前的辦公室助理,還要付一

【BX】loop指令

完整描述一個記憶體單元的條件:①記憶體單元的地址。②記憶體單元的長度。 用符號“()”來表示一個暫存器或一個記憶體單元中的內容。eg:(ax)、(al)。“()”中表示的資料有兩種:①位元組;②字。是哪種型別由暫存器名或具體的運算決定。 注意:①()中的元素可以有3中型別:①暫存器名;②段暫存器名;③記憶

《微機原理與介面技術》——定時器/計數器8253A

雖然寫這個部落格主要目的是為了給我自己做一個思路記憶錄,但是如果你恰好點了進來,那麼先對你說一聲歡迎。我並不是什麼大觸,只是一個菜菜的學生,如果您發現了什麼錯誤或者您對於某些地方有更好的意見,非常歡迎您的斧正! 第1節——定時器和計數器的概念 計數/Counter:統計某物件的數量(典型的計

演算法班筆記 二叉樹基於樹的DFS

第五章 二叉樹和基於樹的DFS 在這一章節的學習中,我們將要學習一個數據結構——二叉樹(Binary Tree),和基於二叉樹上的搜尋演算法。 在二叉樹的搜尋中,我們主要使用了分治法(Divide Conquer)來解決大部分的問題。之所以大部分二叉樹的問題可以使用分治法

翻譯:libevent參考手冊:輔助型別函式 (七) (轉)

這些巨集訪問和操作套接字錯誤程式碼。EVUTIL_SOCKET_ERROR()返回本執行緒最後一次套接字操作的全域性錯誤號,evutil_socket_geterror()則返回某特定套接字的錯誤號。(在類Unix系統中都是errno)EVUTIL_SET_SOCKET_ERROR()修改當前套接字錯誤號(與

類的繼承super的使用呼叫父類構造方法

類的繼承 mammals dogs cats humans lions tigers leopards 人是哺乳動物,因為人都具有哺乳動物的所有特徵,但哺乳動物卻不一定是人。哺乳動物類和人類之間就存在繼承關係(IS-A

構建 團隊流程

ini 之前 組織 第五章 團隊 mod 交互 然而 逆轉 典型的團隊開發模式和流程,完全是新的內容;涉及到更多的術語和有意思的策略性東西 1.團隊模式【我比較認可的】 主治醫師模式 由首席程序員(相當於首席醫生)負責整個工程,周圍人員各司其職,配合支持中心人物的工作;

讀構建:團隊流程

min 這樣的 程序員 希望 成員 eat 貢獻 核心 不能 團隊有一致的集體目標,團隊要一起完成這目標。一個團隊的成員不一定要同時工作,例如接力賽跑。 團隊成員有各自的分工,互相依賴合作,共同完成任務。 軟件團隊有各種形式,適用於不同的人員和需求。基於直覺形成的團隊模式未

構建法( 團隊流程)

功能 實用 運用 驗證 的人 秘密 開發 個性化 社區 第五章主要講了典型的軟件團隊模式和開發流程。以及我們也將討論團隊模式和開發效率之間的一些關系。 1.非團隊和團隊 團隊的主要特點: 1) 團隊有一致的集體目標,團隊要一起完成這個目標。一個團

Linux學習路:檔案目錄管理(1)

備註:屬於個人分享,文章如有問題請留言,謝謝! 第五章檔案和目錄管理 1、絕對路徑和相對路徑 絕對路徑:一定是由根目錄(/)寫起的,例如:cd /etc/sysconfig 相對路徑:不是由根目錄(/)寫起,例如:cd etc 命令cd          cd切換

:ffmpegQT開發播放器使用QT播放

寫在前面:    編寫完視訊的編碼轉碼程式之後,就需要將整個程式重新封裝一下,以便於後續的工作,這裡對應視訊課程中的4-1~4-2。前陣子忙著工作上的事情,也就沒什麼進度,想想還是不應該,QT稍微接觸了

Linux學習路:檔案目錄管理(2)

備註:屬於個人分享,文章如有問題請留言,謝謝! 第五章檔案和目錄管理 4、檔案的所有者和所屬組 所有者:是指此檔案由這個使用者建立,即檔案的擁有著 所屬組:這個檔案屬於哪一個使用者組 使用命令ls –l 顯示出來 第3列是所有者 第4列是所屬組 5、Lin

C語言程式設計入門--C語言基本運算表示式-part1

  導讀:程式要完成高階功能,首先要能夠做到基本的加減乘除。本章從程式中變數的概念開始,結合之前學的輸出函式和新介紹的輸入函式製作簡單人機互動程式,然後講解最基礎的加減法運算,自制簡單計算器程式練手。    5.1 變數 5.1.1 變數宣告定義與賦值表示式   上一章講了資料型別,資料型別要和變數結合在一起