第五章 SpringMVC之ViewResolver和View解析
過完年了,本來是想在年前將SpringMVC系列寫完的,只是在接近年末的時候沒有了一種學習心態,這兩天看了一下ViewResolver原始碼,就想盡快將這篇部落格寫出,也好完結SpringMVC的系列部落格並開始下面的學習。
自己寫的配置檔案springController.xml中和ViewResolve有關的部分
自己寫的處理器處理使用者的請求,在處理完請求後返回一個ModelAndView物件<!--檢視解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- webroot到一指定資料夾檔案路徑 --> <property name="prefix" value="/"/> <!-- 檢視名稱字尾 --> <property name="suffix" value=".jsp"/> </bean>
可以看出處理器返回MdoelAndView物件並向此物件中設一個viewName,ViewName是邏輯檢視名,還向MdoelAndView中傳入了數值,其實就是向MdoelAndView傳入了一個Map物件,這個Map物件就是我們常說的資料模型Modle。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; } }
一.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方法AbstractView的exposeModelAsRequestAttributes方法,在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後再來寫。
相關推薦
第五章 SpringMVC之ViewResolver和View解析
過完年了,本來是想在年前將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切換
第五章:ffmpeg和QT開發播放器之使用QT播放
寫在前面: 編寫完視訊的編碼轉碼程式之後,就需要將整個程式重新封裝一下,以便於後續的工作,這裡對應視訊課程中的4-1~4-2。前陣子忙著工作上的事情,也就沒什麼進度,想想還是不應該,QT稍微接觸了
Linux學習之路:第五章檔案和目錄管理(2)
備註:屬於個人分享,文章如有問題請留言,謝謝! 第五章檔案和目錄管理 4、檔案的所有者和所屬組 所有者:是指此檔案由這個使用者建立,即檔案的擁有著 所屬組:這個檔案屬於哪一個使用者組 使用命令ls –l 顯示出來 第3列是所有者 第4列是所屬組 5、Lin
C語言程式設計入門之--第五章C語言基本運算和表示式-part1
導讀:程式要完成高階功能,首先要能夠做到基本的加減乘除。本章從程式中變數的概念開始,結合之前學的輸出函式和新介紹的輸入函式製作簡單人機互動程式,然後講解最基礎的加減法運算,自制簡單計算器程式練手。 5.1 變數 5.1.1 變數宣告定義與賦值表示式 上一章講了資料型別,資料型別要和變數結合在一起