日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringMVC核心——视图渲染(包含视图解析)问题

發(fā)布時(shí)間:2024/7/19 javascript 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringMVC核心——视图渲染(包含视图解析)问题 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、本來想說的是返回值處理問題,但在 SpringMVC 中,返回值處理問題的核心就是視圖渲染。所以這里標(biāo)題叫視圖渲染問題。

本來想在上一篇文章中對(duì)視圖解析進(jìn)行說明的,但是通過源碼發(fā)現(xiàn),它應(yīng)該算到視圖渲染中,所以在這篇文章中進(jìn)行說明

org.springframework.web.servlet.DispatcherServlet#doDispatch方法中

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//945行返回了 ModelAndView 對(duì)象 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);// 959行進(jìn)行的就是返回值處理問題

org.springframework.web.servlet.DispatcherServlet#processDispatchResult方法中

render(mv, request, response); //1012進(jìn)行視圖的渲染(包含視圖解析)

org.springframework.web.servlet.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()) {// We need to resolve the view name.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() + "'");}try {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;}}

可以看到有兩個(gè) if ,第一個(gè) if 解決的是視圖解析問題,第二個(gè) if 解決的是視圖渲染問題。還有官方是這樣描述這個(gè)方法的:Render the given ModelAndView.

二、視圖解析:通過視圖解析器進(jìn)行視圖的解析

1.解析一個(gè)視圖名到一個(gè)視圖對(duì)象,具體解析的過程是:在容器中查找所有配置好的視圖解析器(List類型),然后進(jìn)行遍歷,

只要有一個(gè)視圖解析器能解析出視圖就返回 View 對(duì)象,若遍歷完成后都不能解析出視圖,那么返回 null。

具體來看:

org.springframework.web.servlet.DispatcherServlet#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; }

2. ViewResolver

(1)官方描述:

* Interface to be implemented by objects that can resolve views by name.
*
* <p>View state doesn't change during the running of the application,
* so implementations are free to cache views.
*
* <p>Implementations are encouraged to support internationalization,
* i.e. localized view resolution.

?

?

?

?

?

?

說明:

ViewResolver 接口由能解析視圖名稱的實(shí)現(xiàn)類來實(shí)現(xiàn)。

在程序運(yùn)行期間視圖的狀態(tài)不能更改,所以實(shí)現(xiàn)能被隨意緩存。鼓勵(lì)實(shí)現(xiàn)支持國際化。

(2)ViewResolver 的整個(gè)體系

可以看出 SpringMVC 提供了很多類型視圖解析器。

(3)在 SpringMVC 的第一篇文章中,配置過一個(gè)視圖解析器。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">   <property name="prefix" value="/WEB-INF/views/"/>   <property name="suffix" value=".jsp"/> </bean>

發(fā)送一個(gè)請求之后,發(fā)現(xiàn)要遍歷的 ViewResolvers 只有一個(gè),就是上面的這個(gè) ViewResolver。沒有其他默認(rèn)的視圖解析器。所以說在SpringMVC 配置文件中,必須配置至少一個(gè) 視圖解析器。

那么這里會(huì)有一個(gè)問題?如果配置多個(gè)視圖解析器,他們的遍歷順序是怎么樣的呢?

ViewResolver 的所有實(shí)現(xiàn)類中都存在一個(gè) order 屬性。

看這個(gè)屬性的 setOrder() 注釋:Set the order in which this {@link org.springframework.web.servlet.ViewResolver}?is evaluated。設(shè)置誰先被評(píng)估。

還有一點(diǎn)小不同:

除?ContentNegotiatingViewResolver 之外,其他所有的 ViewResolver 的默認(rèn)值都是:Integer.MAX_VALUE(2^31 -1,即2147483647),

而 ContentNegotiatingViewResolver 的默認(rèn)值為?Ordered.HIGHEST_PRECEDENCE(-2147483648)。

那他們的遍歷的順序與 order 是什么關(guān)系呢?如果不設(shè)置 order 的話,遍歷順序又是怎么樣的?

<1>設(shè)置 order 屬性后,遍歷順序是怎么樣的

<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">   <property name="order" value="99"/> </bean><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">   <property name="prefix" value="/WEB-INF/views/"/>   <property name="suffix" value=".jsp"/>   <property name="order" value="-99"/> </bean>

對(duì) BeanNameViewResolver 的 order 屬性指定為 99,對(duì) InternalResourceViewResolver 指定為-99。這里故意將 BeanNameViewResolver 放到了 InternalResourceViewResolver 前面。

遍歷順序:

發(fā)現(xiàn)?InternalResourceViewResolver 會(huì)先被遍歷。

結(jié)論:

在指定 order 屬性的情況下,order 值越小的,越先會(huì)遍歷。

<2>不設(shè)置 order 屬性,遍歷順序是怎樣的

在測試這個(gè)的時(shí)候,發(fā)現(xiàn)一個(gè)這樣的現(xiàn)象:我將?BeanNameViewResolver 和?InternalResourceViewResolver 的 order 屬性都去掉,我這里用的是 Jrebel 的熱部署。發(fā)現(xiàn)再次請求的時(shí)候,

這兩個(gè)視圖的 order 屬性值還和之前的一樣:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">   <property name="prefix" value="/WEB-INF/views/"/>   <property name="suffix" value=".jsp"/> </bean><bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> </bean>

為什么呢?想起了官方的描述:"在程序運(yùn)行期間視圖的狀態(tài)不能更改,所以實(shí)現(xiàn)能被隨意緩存",這里被緩存了。重啟后來看真正的測試。

第一種情況:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">   <property name="prefix" value="/WEB-INF/views/"/>   <property name="suffix" value=".jsp"/> </bean><bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> </bean>

第二種情況:將 bean 在 SpringMVC ?Config 文件中的順序進(jìn)行替換,需要注意的是,重啟服務(wù)器,否則它們的順序還是會(huì)被緩存下來。重啟后來看:

<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> </bean><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">   <property name="prefix" value="/WEB-INF/views/"/>   <property name="suffix" value=".jsp"/> </bean>

結(jié)論已經(jīng)很明顯了:

在同等優(yōu)先級(jí)的情況下,遍歷的順序是由 ViewResolver 在 SpringMVC Config 文件中配置的順序決定的,誰在前誰先遍歷。

這里不對(duì)具體的每個(gè)視圖解析器進(jìn)行說明,路已經(jīng)指明了。

3.ViewResolver 具體是怎么將 view name 解析為一個(gè)視圖的?

先看 ViewResolver 中的 View resolveViewName(String viewName, Locale locale)

說明:

解析視圖通過其名稱。注意:允許 ViewResolver 鏈。

如果一個(gè)給定名稱的view 沒有在一個(gè) ViewResolver 中定義,那么它應(yīng)該返回 null。

然而它也不是必須的:有一些 ViewResolver 當(dāng)嘗試通過視圖名稱構(gòu)建 View 對(duì)象失敗后,不返回 null。而替代它的是,拋出一個(gè)異常。

?

以 org.springframework.web.servlet.view.AbstractCachingViewResolver 進(jìn)行分析。

@Override public View resolveViewName(String viewName, Locale locale) throws Exception {if (!isCache()) {     return createView(viewName, locale);}   else {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) {         // Ask the subclass to create the View object.view = createView(viewName, locale);         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);} }

判斷該視圖是否被緩存,如果沒有被緩存,則創(chuàng)建視圖,如果被緩存,則從緩存中獲取。

創(chuàng)建視圖,以 InternalResourceViewResolver 和 BeanNameViewResolver 為例:

(1)InternalResourceViewResolver?

org.springframework.web.servlet.view.UrlBasedViewResolver#createView

protected View createView(String viewName, Locale locale) throws Exception {// If this resolver is not supposed to handle the given view,// return null to pass on to the next resolver in the chain.if (!canHandle(viewName, locale)) {return null;}// Check for special "redirect:" prefix.if (viewName.startsWith(REDIRECT_URL_PREFIX)) {String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());return applyLifecycleMethods(viewName, view);}// Check for special "forward:" prefix.if (viewName.startsWith(FORWARD_URL_PREFIX)) {String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());return new InternalResourceView(forwardUrl);}// Else fall back to superclass implementation: calling loadView.   return super.createView(viewName, locale); }

在創(chuàng)建視圖前會(huì)檢查返回值是否是以:"redirect:" 或 "forward:" 開頭的。

如果是重定向:則創(chuàng)建一個(gè)重定向視圖,返回創(chuàng)建的視圖。如果是轉(zhuǎn)發(fā):則返回通過 轉(zhuǎn)發(fā) url 創(chuàng)建的 InternalResourceView?視圖。

org.springframework.web.servlet.view.UrlBasedViewResolver#loadView

AbstractUrlBasedView view = buildView(viewName); View result = applyLifecycleMethods(viewName, view); return (view.checkResource(locale) ? result : null);

調(diào)用具體的?InternalResourceViewResolver ,然后又調(diào)用 父類的 buildView() 方法

org.springframework.web.servlet.view.UrlBasedViewResolver#buildView

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());   if (this.exposePathVariables != null) {view.setExposePathVariables(exposePathVariables);}   return view; }

可以看出:是通過?BeanUtils.instantiateClass(getViewClass()) 來創(chuàng)建 View 對(duì)象的。這個(gè)例子與其說是 InternalResourceViewResolver ,倒不如說是?UrlBasedViewResolver 類型的例子。

從這里也可以看出:該類型最終要到的目標(biāo)URL為:getPrefix() + viewName + getSuffix()

(2)BeanNameViewResolver?

public View resolveViewName(String viewName, Locale locale) throws BeansException {ApplicationContext context = getApplicationContext();if (!context.containsBean(viewName)) {// Allow for ViewResolver chaining.return null;}return context.getBean(viewName, View.class); }

org.springframework.context.support.AbstractApplicationContext#getBean(java.lang.String, java.lang.Class<T>)

public <T> T getBean(String name, Class<T> requiredType) throws BeansException {this.assertBeanFactoryActive();return this.getBeanFactory().getBean(name, requiredType); }

可以看出:是通過 BeanFactory.getBean(String name, Class<T> requiredType) 來獲取的。

三、視圖渲染

1.View

官方文檔:

* MVC View for a web interaction. Implementations are responsible for rendering
* content, and exposing the model. A single view exposes multiple model attributes.
*
* <p>This class and the MVC approach associated with it is discussed in Chapter 12 of
* <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">Expert One-On-One J2EE Design and Development</a>
* by Rod Johnson (Wrox, 2002).
*
* <p>View implementations may differ widely. An obvious implementation would be
* JSP-based. Other implementations might be XSLT-based, or use an HTML generation library.
* This interface is designed to avoid restricting the range of possible implementations.
*
* <p>Views should be beans. They are likely to be instantiated as beans by a ViewResolver.
* As this interface is stateless, view implementations should be thread-safe.

?

?

?

?

?

?

?

?

?

?

說明:

SpringMVC 對(duì)一個(gè) web 來說是相互作用的(不太明白)。View 的實(shí)現(xiàn)類是負(fù)責(zé)呈現(xiàn)內(nèi)容的,并且 exposes(暴露、揭露、揭發(fā)的意思,這里就按暴露解釋吧,想不出合適的詞語) 模型的。

一個(gè)單一的視圖可以包含多個(gè)模型。

View 的實(shí)現(xiàn)可能有很大的不同。一個(gè)明顯的實(shí)現(xiàn)是基于 JSP 的。其他的實(shí)現(xiàn)可能是基于 XSLT 的,或者是一個(gè) HTML 生成庫。

設(shè)計(jì)這個(gè)接口是為了避免約束可能實(shí)現(xiàn)的范圍(這里是不是說,我們可以通過實(shí)現(xiàn)該接口來自定義擴(kuò)展自定義視圖?)。

所有的視圖都應(yīng)該是一個(gè) Bean 類。他們可能被 ViewResolver 當(dāng)做一個(gè) bean 進(jìn)行實(shí)例化。

由于這個(gè)接口是無狀態(tài)的,View 的所有實(shí)現(xiàn)類應(yīng)該是線程安全的。

2.View 的整個(gè)體系

3.具體渲染的一個(gè)過程

org.springframework.web.servlet.view.AbstractView#render

說明一下這個(gè)方法:

為指定的模型指定視圖,如果有必要的話,合并它靜態(tài)的屬性和RequestContext中的屬性,renderMergedOutputModel() 執(zhí)行實(shí)際的渲染。

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);}Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);prepareResponse(request, response);renderMergedOutputModel(mergedModel, request, response); }

這里只看?org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel 這個(gè)方法

@Override 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.if (logger.isDebugEnabled()) {logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");}rd.forward(requestToExpose, response);} }

可以看到前面的幾個(gè)步驟都是為 RequestDispatch 做準(zhǔn)備,裝填數(shù)據(jù)。最后,到目標(biāo)頁面是通過轉(zhuǎn)發(fā)。

四、總結(jié)

介紹了 SpringMVC 視圖解析和視圖渲染問題是如何解決的。SpringMVC 為邏輯視圖提供了多種視圖解析策略,可以在配置文件中配置多個(gè)視圖的解析策略。并制定其先后順序。

這里所說的視圖解析策略,就是指視圖解析器。視圖解析器會(huì)將邏輯視圖名解析為一個(gè)具體的視圖對(duì)象。再說視圖渲染的過程,視圖對(duì)模型進(jìn)行了渲染,最終將模型的數(shù)據(jù)以某種形式呈現(xiàn)給用戶。

?

到此為止,在我看來,SpringMVC 整個(gè)流程已經(jīng)跑完,前面的幾篇文章,從一個(gè)小栗子開始,然后分別介紹了請求映射問題,參數(shù)問題,返回值問題,到這篇文章,返回值處理問題

我認(rèn)為這幾個(gè)問題是整個(gè) SpringMVC 的核心內(nèi)容。其他的問題,都是建立在該問題的基礎(chǔ)上,或者是對(duì)這幾個(gè)問題的一種延伸。

?

還有想說的是,在邊測試邊看源碼邊寫文章的時(shí)候,給我這么一個(gè)感覺,不論是何種開源框架,它所有的應(yīng)用都是從源碼中來的,不論哪個(gè)人或者那本書把這個(gè)知識(shí)講的多好,

但是在我看來,想要學(xué)好某個(gè)框架,都要到它源碼中去,找到它的根,這樣才會(huì)有理有據(jù),不會(huì)忘的那么快,縱然忘了,下次還是可以找出來為什么。

縱然在看源碼的過程中,可能會(huì)遇到很多困難,比如英文單詞不認(rèn)識(shí),整個(gè)句子讀不通,但是如果你能結(jié)合自己的理解,

然后理解文檔中的話,我相信對(duì)你幫助是很大的,其實(shí)這就是一種自學(xué)能力,摸的著,看得見。

?

算是給各位看客老爺?shù)囊环N建議吧,愿各位在學(xué)習(xí)的道路上不要停滯不前。

?

轉(zhuǎn)載于:https://www.cnblogs.com/solverpeng/p/5743609.html

總結(jié)

以上是生活随笔為你收集整理的SpringMVC核心——视图渲染(包含视图解析)问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。