Spring MVC檢視解析器
Spring MVC的檢視解析器
什麼是檢視解析器
在我們學習Spring MVC時,當我們傳送請求給Spring MVC控制的資源時被DispatcherServlet處理,Spring會分析看所有的HandleMapping中定義的請求對映中最合理的那個Handle,並通過HandleMapping得到該Handle,交給HandleAdapter處理Handle並返回一個ModelAndView物件。獲取到ModelAndView物件後,Spring會把View渲染交給使用者。在渲染View的過程中,發揮作用的就是ViewResolver和View。有時ModelAndView中不包含真正的檢視,而是一個邏輯檢視名的時候,ViewResolver就會根據規則將邏輯檢視名解析成真正的View物件,View物件才是真正進行渲染的
通過上面的流程解釋,我們直到ViewResolver
和View
是Spring MVC檢視解析的中最重要的介面。Spring MVC給我們提供了非常多的檢視解析器,我們可以先講一下什麼是檢視?
View介面的重要實現類
檢視基礎介面,它的各種實現類是無狀態的,因此是執行緒安全的
在此簡單介紹一下其中兩個重要的View實現類:
其中的renderMergedOutputModel是AbstractView抽象類中的重寫方法,實際上就是給View的render提供服務,render才是View物件的渲染。
重定向:RedirectView
這個檢視跟重定向有關,也是重定向問題的核心。
首先通過原始碼看渲染過程:
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws IOException { String targetUrl = this.createTargetUrl(model, request); targetUrl = this.updateTargetUrl(targetUrl, model, request, response); FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request); if (!CollectionUtils.isEmpty(flashMap)) { UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build(); flashMap.setTargetRequestPath(uriComponents.getPath()); flashMap.addTargetRequestParams(uriComponents.getQueryParams()); FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); if (flashMapManager == null) { throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set"); } flashMapManager.saveOutputFlashMap(flashMap, request, response); } this.sendRedirect(request, response, targetUrl, this.http10Compatible); }
看到String targetUrl = this.createTargetUrl(model, request);
這個作用就是建立Url重定向的路徑。
最重要的來了,看到最後一行this.sendRedirect(request, response, targetUrl, this.http10Compatible);
,原來最後是使用了sendRedirect重定向到targetUrl
。
其中還有重要的就是構造路徑的過程,有興趣可以去研究原始碼,有需求可以在重定向時傳遞資料,在url中顯示,這個就是在構造路徑中實現的。
請求轉發:InternalResourceView
檢視渲染過程:
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
this.exposeModelAsRequestAttributes(model, request);
this.exposeHelpers(request);
String dispatcherPath = this.prepareForRendering(request, response);
RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl( ) + "]: Check that the corresponding file exists within your web application archive!");
} else {
if (this.useInclude(request, response)) {
response.setContentType(this.getContentType());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Including resource [" + this.getUrl() + "] in InternalResourceView '" + this.getBeanName() + "'");
}
rd.include(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Forwarding to resource [" + this.getUrl() + "] in InternalResourceView '" + this.getBeanName() + "'");
}
rd.forward(request, response);
}
}
}
咱們暫時只要關注兩個點即可rd.include(request, response);
和rd.forward(request, response);
這個兩個方法,根據判斷條件來決定執行哪個方法到指定的jsp頁面。
ViewResolver檢視直譯器介面
ViewResolver介面中只有一個方法:
View resolveViewName(String viewName, Locale locale) throws Exception;
根據檢視名稱viewName和Local物件來獲取View物件,這就是檢視直譯器的作用。
那我們來解釋ViewResolver介面的一個常用的實現類InternalResourceViewResolver
InternalResourceViewResolver
繼承自UrlBasedViewResolver,以InternalResourceView作為檢視,若專案中存在“javax.servlet.jsp.jstl.core.Config”該類,那麼會以JstlView作為檢視。重寫了buildView方法,主要就是為了給InternalResourceView檢視設定屬性。
所以我們從中得知InternalResourceViewResolver是以InternalResourceView作為檢視也就是說它只能處理請求轉發,處理不了重定向! 然後我們再看看UrlBasedViewResolver
是什麼?
UrlBasedViewResolver
UrlBasedViewResolver繼承自AbstractCachingViewResolver抽象類、並實現Ordered介面的類
AbstractCachingViewResolver抽象類:
- AbstractCachingViewResolver是一個抽象類,這種檢視解析器會把它曾經解析過的檢視儲存起來,然後每次要解析檢視的時候先從快取裡面找,如果找到了對應的檢視就直接返回,如果沒有就建立一個新的檢視物件,然後把它放到一個用於快取的map中,接著再把新建的檢視返回。使用這種檢視快取的方式可以把解析檢視的效能問題降到最低。
- AbstractCachingViewResolver是帶有快取功能的ViewResolver介面基礎實現抽象類,該類有個屬性名為viewAccessCache的以 "viewName_locale" 為key, View介面為value的Map。該抽象類實現的resolveViewName方法內部會呼叫createView方法,方法內部會呼叫loadView抽象方法。
Ordered介面
實現了相同介面的實現類優先順序問題,也就是將相同介面的實現類進行排序。後期學習工作原理的時候就會理解透徹。
OK瞭解UrlBasedViewResolver過後,我們再來看看他的屬性和方法
- viewClass 檢視的型別
- prefix 檢視名稱的字首
- suffix 檢視名稱的字尾
這三屬性比較重要,檢視的型別能理解,但是字首和字尾是什麼意思?
這個時候就能體現UrlBasedViewResolver的重要性了,當我們根據請求得到ModelAndView時,可能只包含的邏輯路徑並不是完整的,比如真實路徑是/a/b/c/d/e.jsp
,但是指定viewName時卻是“e”,這個時候UrlBasedViewResolver的字首字尾就發揮作用了:字首+viewName+字尾,所以字首就可以設定為/a/b/c/d/
而後綴就可以設為.jsp
這樣就解決了這個問題,同時在遇到多個jsp檔案放在WEB-INF
的多級目錄下,這個時候作用很大,節省了程式碼,但是必須指定viewClass(檢視型別)。
可以通過看原始碼的createView()
核心方法來理解產生View的原理
大致一眼看去主要產生的View的型別有兩種,一個是以"redirect:"為字首,型別是RedirectView。另一種是以"forward:"為字首,型別是InternalResourceView。
而我們會常用InternalResourceViewResolver這個檢視解析器,它繼承了UrlBasedViewResolver,看初始化的原始碼:
其中無參構造器中設定了Class<?> viewClass=this.requiredViewClass();
protected Class<?> requiredViewClass() {
return InternalResourceView.class;
}
不難看出InternalResourceViewResolver已經規定了它只能處理InternalResourceView型別的檢視,也就是隻能解析請求轉發,而重定向則不能。有參構造也就是設定了字首和字尾。使用InternalResourceViewResolver的案例就不演示了,網上一大把。就是記錄一下學習檢視解析器的筆記。
總結
檢視解析器是Spring MVC為之重要的一個環節,主要是依靠ViewResolver
和View
- ViewResolver根據ModelAndView的邏輯檢視名解析出View物件
- View則將檢視渲染返回給瀏覽器呈現給使用者
檢視解析器的表層含義也就理解到這裡,更深層次的理解也需要更深入原始碼。有什麼錯誤請指出。