前言
成都創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括梅縣網(wǎng)站建設(shè)、梅縣網(wǎng)站制作、梅縣網(wǎng)頁制作以及梅縣網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,梅縣網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到梅縣省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
在RequestMappingHandlerAdapter對request進(jìn)行了適配,并且調(diào)用了目標(biāo)handler之后,其會返回一個ModelAndView對象,該對象中主要封裝了兩個屬性:view和model。其中view可以是字符串類型也可以是View類型,如果是字符串類型,則表示邏輯視圖名,如果是View類型,則其即為我們要轉(zhuǎn)換的目標(biāo)view;這里model是一個Map類型的對象,其保存了渲染視圖所需要的屬性。本文主要講解Spring是如何通過用戶配置的ViewResolver來對視圖進(jìn)行解析,并且聲稱頁面進(jìn)行渲染的。
首先我們來看一個比較典型的ViewResolver配置:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean>
這里配置的ViewResolver是InternalResourceViewResolver,其主要有兩個屬性:prefix和suffix。在進(jìn)行視圖解析時,如果ModelAndView中的view是字符串類型的,那么要解析的視圖存儲位置就通過“prefix + (String)view + suffix”的格式生成要解析的文件路徑,并且將其封裝為一個View對象,最后通過View對象來渲染具體的視圖。前面講到,ModelAndView中view也可以是View類型的,如果其是View類型的,那么這里就可以跳過第一步,直接使用其提供的View對象進(jìn)行視圖解析了。
由上面的講解可以看出,對于視圖的解析可以分為兩個步驟:①解析邏輯視圖名;②渲染視圖。對應(yīng)于這兩步,Spring也抽象了兩個接口:ViewResolver和View,這兩個接口的聲明分別如下:
public interface ViewResolver { // 通過邏輯視圖名和用戶地區(qū)信息生成View對象 View resolveViewName(String viewName, Locale locale) throws Exception; }
public interface View { // 獲取返回值的contentType default String getContentType() { return null; } // 通過用戶提供的模型數(shù)據(jù)與視圖信息渲染視圖 void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
從上面兩個接口的聲明可以看出,ViewResolver的作用主要在于通過用戶提供的邏輯視圖名根據(jù)一定的策略生成一個View對象,而View接口則負(fù)責(zé)根據(jù)視圖信息和需要填充的模型數(shù)據(jù)進(jìn)行視圖的渲染。這里我們首先看InternalResourceViewResolver是如何解析視圖名的,如下是其具體實(shí)現(xiàn)方式:
@Override @Nullable public View resolveViewName(String viewName, Locale locale) throws Exception { // 判斷當(dāng)前ViewResolver是否設(shè)置了需要對需要解析的視圖進(jìn)行緩存,如果不需要緩存, // 則每次請求時都會重新解析生成視圖對象 if (!isCache()) { // 根據(jù)視圖名稱和用戶地區(qū)信息創(chuàng)建View對象 return createView(viewName, locale); } else { // 如果可以對視圖進(jìn)行緩存,則首先獲取緩存使用的key,然后從緩存中獲取該key,如果沒有取到, // 則對其進(jìn)行加鎖,再次獲取,如果還是沒有取到,則創(chuàng)建一個新的View,并且對其進(jìn)行緩存。 // 這里使用的是雙檢查法來判斷緩存中是否存在對應(yīng)的邏輯視圖。 Object cacheKey = getCacheKey(viewName, locale); View view = this.viewAccessCache.get(cacheKey); if (view == null) { synchronized (this.viewCreationCache) { view = this.viewCreationCache.get(cacheKey); if (view == null) { view = createView(viewName, locale); // 這里cacheUnresolved指的是是否緩存默認(rèn)的空視圖,UNRESOLVED_VIEW是 // 一個沒有任何內(nèi)容的View if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } if (view != null) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); if (logger.isTraceEnabled()) { logger.trace("Cached view [" + cacheKey + "]"); } } } } } return (view != UNRESOLVED_VIEW ? view : null); } }
上面代碼中,InternalResourceViewResolver主要是判斷了當(dāng)前是否配置了需要緩存生成的View對象,如果需要緩存,則從緩存中取,如果沒有配置,則每次請求時都會重新生成新的View對象。這里我們繼續(xù)看其是如何創(chuàng)建視圖的:
@Override protected View loadView(String viewName, Locale locale) throws Exception { // 使用邏輯視圖名按照指定規(guī)則生成View對象 AbstractUrlBasedView view = buildView(viewName); // 應(yīng)用聲明周期函數(shù),也就是調(diào)用View對象的初始化函數(shù)和Spring用于切入bean創(chuàng)建的 // Processor和Aware函數(shù) View result = applyLifecycleMethods(viewName, view); // 檢查view的準(zhǔn)確性,這里默認(rèn)始終返回true return (view.checkResource(locale) ? result : null); } // 這里buildView()方法主要是根據(jù)邏輯視圖名生成一個View對象 protected AbstractUrlBasedView buildView(String viewName) throws Exception { // 對于InternalResourceViewResolver而言,其返回的View對象的 // 具體類型是InternalResourceView Class<?> viewClass = getViewClass(); Assert.state(viewClass != null, "No view class"); // 使用反射生成InternalResourceView對象實(shí)例 AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass); // 這里可以看出,InternalResourceViewResolver獲取目標(biāo)視圖的方式就是將用戶返回的 // viewName與prefix和suffix進(jìn)行拼接,以供View對象直接讀取 view.setUrl(getPrefix() + viewName + getSuffix()); // 設(shè)置View的contentType屬性 String contentType = getContentType(); if (contentType != null) { view.setContentType(contentType); } // 設(shè)置contextAttribute和attributeMap等屬性 view.setRequestContextAttribute(getRequestContextAttribute()); view.setAttributesMap(getAttributesMap()); // 這了pathVariables表示request請求url中的屬性,這里主要是設(shè)置是否將這些屬性暴露到視圖中 Boolean exposePathVariables = getExposePathVariables(); if (exposePathVariables != null) { view.setExposePathVariables(exposePathVariables); } // 這里設(shè)置的是是否將Spring的bean暴露在視圖中,以供給前端調(diào)用 Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes(); if (exposeContextBeansAsAttributes != null) { view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes); } // 設(shè)置需要暴露給前端頁面的bean名稱 String[] exposedContextBeanNames = getExposedContextBeanNames(); if (exposedContextBeanNames != null) { view.setExposedContextBeanNames(exposedContextBeanNames); } return view; } protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) { ApplicationContext context = getApplicationContext(); if (context != null) { // 對生成的View對象應(yīng)用初始化方法,主要包括InitializingBean.afterProperties()和一些 // Processor,Aware方法 Object initialized = context.getAutowireCapableBeanFactory() .initializeBean(view, viewName); if (initialized instanceof View) { return (View) initialized; } } return view; }
從上面對于視圖名稱的解析,可以看出,其主要做了四部分工作:①實(shí)例化View對象;②設(shè)置目標(biāo)視圖地址;③初始化視圖的一些基本屬性,如需要暴露的bean對象;④調(diào)用View對象的初始化方法對其進(jìn)行初始化。從這里的生成View對象的過程也可以看出,ViewResolver生成的View對象只是保存了目標(biāo)view的地址,而對其加載和渲染的過程主要是委托給了View對象進(jìn)行的。下面我們就來看一下InternalResourceView是如何結(jié)合具體的model來渲染視圖的:
@Override public void render(@Nullable 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); } // 這里主要是將request中pathVariable,staticAttribute與用戶返回的model屬性 // 合并為一個Map對象,以供給后面對視圖的渲染使用 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); // 判斷當(dāng)前View對象的類型是否為文件下載類型,如果是文件下載類型,則設(shè)置response的 // Pragma和Cache-Control等屬性值 prepareResponse(request, response); // 通過合并的model數(shù)據(jù)以及視圖地址進(jìn)行視圖的渲染 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
這里對于視圖的渲染主要分為了三步:①合并用戶返回的model數(shù)據(jù)和request中的pathVariable與staticAttribute等數(shù)據(jù);②判斷當(dāng)前是否為文件下載類型的視圖解析,如果是,則設(shè)置Pragma和Cache-Control等header;③通過合并的模型數(shù)據(jù)和request請求對視圖進(jìn)行渲染。這里我們主要看一下renderMergedOutputModel()方法是如何對視圖進(jìn)行渲染的:
@Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 這里主要是對model進(jìn)行遍歷,將其key和value設(shè)置到request中,當(dāng)做request的 // 一個屬性供給頁面調(diào)用 exposeModelAsRequestAttributes(model, request); // 提供的一個hook方法,默認(rèn)是空實(shí)現(xiàn),用于用戶進(jìn)行request屬性的自定義使用 exposeHelpers(request); // 檢查當(dāng)前是否存在循環(huán)類型的視圖名稱解析,主要是根據(jù)相對路徑進(jìn)行判斷視圖名是無法解析的 String dispatcherPath = prepareForRendering(request, response); // 獲取當(dāng)前request的RequestDispatcher對象,該對象有兩個方法:include()和forward(), // 用于對當(dāng)前的request進(jìn)行轉(zhuǎn)發(fā),其實(shí)也就是將當(dāng)前的request轉(zhuǎn)發(fā)到另一個url,這里的另一個 // url就是要解析的視圖地址,也就是說進(jìn)行視圖解析的時候請求的對于文件的解析實(shí)際上相當(dāng)于 // 構(gòu)造了另一個(文件)請求,在該請求中對文件內(nèi)容進(jìn)行渲染,從而得到最終的文件。這里的 // include()方法表示將目標(biāo)文件引入到當(dāng)前文件中,與jsp中的include標(biāo)簽作用相同; // forward()請求則表示將當(dāng)前請求轉(zhuǎn)發(fā)到另一個請求中,也就是目標(biāo)文件路徑,這種轉(zhuǎn)發(fā)并不會 // 改變用戶瀏覽器地址欄的請求地址。 RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web " + "application archive!"); } // 判斷當(dāng)前是否為include請求,如果是,則調(diào)用RequestDispatcher.include()方法進(jìn)行文件引入 if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.include(request, response); } else { if (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } // 如果當(dāng)前不是include()請求,則直接使用forward請求將當(dāng)前請求轉(zhuǎn)發(fā)到目標(biāo)文件路徑中, // 從而渲染該視圖 rd.forward(request, response); } }
上述代碼就是進(jìn)行視圖渲染的核心邏輯,上述邏輯主要分為兩個步驟:①將需要在頁面渲染使用的model數(shù)據(jù)設(shè)置到request中;②按照當(dāng)前請求的方式(include或forward)來將當(dāng)前請求轉(zhuǎn)發(fā)到目標(biāo)文件中,從而達(dá)到目標(biāo)文件的渲染。從這里可以看出,實(shí)際上對于Spring而言,其對頁面的渲染并不是在其原始的request中完成的。
本文首先講解了Spring進(jìn)行視圖渲染所需要的兩大組件ViewResolver和View的關(guān)系,然后以InternalResourceViewResolver和InternalResourceView為例講解Spring底層是如何解析一個view,并且渲染該View的。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對創(chuàng)新互聯(lián)的支持。
網(wǎng)頁題目:SpringMVC學(xué)習(xí)教程之視圖深入解析
文章路徑:http://chinadenli.net/article14/jgigde.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供電子商務(wù)、定制開發(fā)、網(wǎng)站設(shè)計(jì)公司、手機(jī)網(wǎng)站建設(shè)、網(wǎng)站策劃、企業(yè)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)