- 什麼是ViewResolver?
從接口的定義來看,ViewResolver就是專門用來生成View的,如下:public interface ViewResolver { /** * viewName就是從controller返回的要跳轉到頁面的字符串 **/ View resolveViewName(String viewName, Locale locale) throws Exception; }
- ViewResolver有哪些?
下面來看一下springmvc給我們默認自帶了那些ViewResolver,如下圖: 從上圖可以看出,有好多ViewResolver,也就是說每個ViewResolver都會生成一個不同View(View是什麼?看我對應的文章)。那麼到底是用哪個ViewResolver呢?根據實際情況,比如,如果我們用的是jsp頁面,那麼我們就需要使用InternalResourceViewResolver來生成對應的View(JstlView,只有這個View,才能跟jsp頁面一同配合使用)。
- 在什麼地方用到ViewResolver?
如果你是從【DispatcherServlet源碼解析】這篇文章過來的,那麼肯定知道,在DispatcherServlet的initViewResolvers方法中,會從容器中獲取ViewResolver的實現類,如下: - 如何把ViewResolver放到容器中?
上面說了ViewResolver是在DispatcherServlet初始化時,會從容器中獲取ViewResolver,那麼如何把ViewResolver中呢?很簡單,方法之一就是,在spring-mvc.xml中(這個文件的名字是會變化的,反正就是一個配合springmvc的xml文件)配置即可,如下: - ViewResolver詳解
上面我們通過圖片展示了springmvc爲我們自帶了很多ViewResolver的實現類,那麼現在我們就來看一下每一個的作用和具體能生成什麼View
1. AbstractCachingViewResolver
這個抽象類實現了ViewResolver,也是很多ViewResolver實現類的基類。把它作爲抽象類然後讓其他實現類繼承,主要是爲了緩存已有的view,然後提高性能。先來看下面一個請求發起後的執行流程:
看到最後一步的resolveViewName方法了吧,這個時候便會調用AbstractCachingViewResolver這個基類中的resolveViewName方法,然後在這裏面再去調用各個子類具體生成View的方法。爲什麼這麼說呢,看一下他源碼:@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) { //② 這裏就是根據多態原理,去調用子類的createView方法創建View view = createView(viewName, locale); if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } if (view != null) { // ③ // 這個基類的唯一所用就是在這,作爲一個緩存,緩存住已經沒有的View // 這樣下次瀏覽器在請求時,可以直接從緩存獲取這個View this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); } } } } return (view != UNRESOLVED_VIEW ? view : null); } }
注意看源碼①,②,③這三個位置,這個類的核心就三處。其中①和③主要是從緩存中查找view和把view放入緩存。②用來調用子類來生成View。
2. UrlBasedViewResolver
這個類的主要作用是通過前綴和後綴設置指定文件的位置和擴展名的。比如一個位於WEB-INF/jsp下的hello.jsp,他們只用這個類的屬性prefix來設置hello.jsp的位置(WEB-INF/jsp),通過suffix指定hello.jsp的擴展名.jsp,然後controller返回hello字符串後,此類會把他們拼在一起組成一個url,然後通過這個url就可以定位到文件位置了(WEB-INF/jsp/hello.jsp)。所有當要使用jsp,freemark等模板是,就可以使用此類來設置文件位置。
這個類是AbstractCachingViewResolver的子類,我們來看下官網對這個類的說明:Simple implementation of the org.springframework.web.servlet.ViewResolver interface, allowing for direct resolution of symbolic view names to URLs, without explicit mapping definition without explicit mapping definition。
Example: prefix="/WEB-INF/jsp/", suffix=".jsp", viewname="test" -> "/WEB-INF/jsp/test.jsp"Supports
AbstractUrlBasedView
subclasses likeInternalResourceView
,VelocityView
andFreeMarkerView
.
意思就是說:它是ViewResolver 的實現類,他可以直接通過nameview與視圖文件(jsp,freemark等文件)的位置進行映射,而無需顯式的進行映射配置(類似於web.xml中servlet那樣爲顯式的進行映射配置)。
例如:通過提前設置好prefix="/WEB-INF/jsp/", suffix=".jsp",然後通過viewname="test" ,就可以映射到"/WEB-INF/jsp/test.jsp"。
他的子類InternalResourceView
,VelocityView
andFreeMarkerView也都具有同樣的特性。
具體的意思,就是說通過提前設定好了jsp文件所在目錄,當controller方法執行完後,便用方法的返回值(字符串)作爲viewname,然後用這個viewname與之前設定好的jsp文件的位置拼接起來,就可以直接定位到jsp文件了,下面用一個例子來具體說明下:
假如下面是我的jsp文件的存放位置 我的controller如下:
我提前設置好jsp文件的路徑爲/WEB-INF/jsp/,文件的擴展名爲.jsp。這時當controller返回hello後,便會通過UrlBasedViewResolver中的方法,將/WEB-INF/jsp/ 和hello和.jsp拼接在一起(/WEB-INF/jsp/hello.jsp),這樣再看,是不是拼接在一起後的值/WEB-INF/jsp/hello.jsp,正好就是我們存放jsp文件中的那個hello.jsp文件的路徑地址了。這就實現了映射。
這樣當controller返回hello後,便會顯示hello.jsp這個文件了(開頭變說過,ViewResolver是用來生成View的,所以拼接的這個地址是在生成View的時候,設置給View的。本段的文字,只是爲了讓大家容易理解,而忽略了內部其他的細節)。
那麼這個提前設置的目錄和擴展名(.jsp),是在哪設置的呢?我貼個圖,你們就一目瞭然了:爲什麼這裏設置的name叫做prefix和suffix呢,因爲prefix和suffix是UrlBasedViewResolver中的屬性。關於爲什麼這麼寫就能給屬性賦值,我就不講了,這屬於spring基礎了。就是在聲明bean的時候,直接當做屬性設置就行了。
這裏要注意的是,我們聲明bean的時候,必須使用UrlBasedViewResolver的子類,因爲我們說過ViewResolver是用來生成View的,具體這個View是什麼(View的具體是那個類),這個是在UrlBasedViewResolver的子類中生成的,每個子類生成的View是不同的。
UrlBasedViewResolver主要負責的是將/WEB-INF/jsp/ 和hello和.jsp拼接在一起(/WEB-INF/jsp/hello.jsp),然後通過多態的的特點,整合子類創建的屬性,然後創建出一個View。源碼如下:protected AbstractUrlBasedView buildView(String viewName) throws Exception { AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass()); view.setUrl(getPrefix() + viewName + getSuffix()); //...省略其他代碼... return view; }
也就是說,要創建一個什麼樣的view(具體view的class),由UrlBasedViewResolver的子類來決定,並賦值到UrlBasedViewResolver的屬性viewClass上,然後,最終由UrlBasedViewResolver來使用這個viewClass通過反射來創建這個View。然後再把拼接好的url(/WEB-INF/jsp/hello.jsp)賦值給這個View。這個類的作用其實就這麼簡單。下面我們再來看看他的子類
另外這個類中還指明:
如果你的viewname以【redirect:】開頭,那麼會創建一個重定向的RedirectView。
如果你的viewname以【forward:】開頭,那麼會創建一個請求轉發的InternalResourceView。
這也就是爲什麼我們經常在controller的方法中會看到例如 【return redirect:***】或者【return forward:***】的寫法的原因了。
3. InternalResourceViewResolver
InternalResourceViewResolver是UrlBasedViewResolver的子類,先來看下官方的說明:Convenient subclass of
UrlBasedViewResolver
that supportsInternalResourceView
(i.e. Servlets and JSPs) and subclasses such asJstlView
.The view class for all views generated by this resolver can be specified via
setViewClass
. SeeUrlBasedViewResolver
's javadoc for details. The default isInternalResourceView
, orJstlView
if the JSTL API is present.BTW, it's good practice to put JSP files that just serve as views under WEB-INF, to hide them from direct access (e.g. via a manually entered URL). Only controllers will be able to access them then.
Note: When chaining ViewResolvers, an InternalResourceViewResolver always needs to be last, as it will attempt to resolve any view name, no matter whether the underlying resource actually exists.
意思就是說:
它是UrlBasedViewResolver
的子類,用來生成InternalResourceView
或者JstlView,生成view後,通過調用父類的setViewClass方法賦值給父類
UrlBasedViewResolver的viewClass屬性。InternalResourceViewResolver默認生成的是InternalResourceView,如果上下文中找到jstl api,那就生成JstlView
順便一提,jsp文件最好放到WEB-INF下,這樣就可以防止通過瀏覽器url直接訪問我們的jsp文件,而只能通過controller去訪問jsp文件。
注意:當有多個ViewResolvers時,InternalResourceViewResolver 必須放在最後一個,因爲他會直接去嘗試根據viewname去找對應的文件,而不關心文件是否存在。從官網說明可以看出:InternalResourceViewResolver主要是用來生成與Jsp搭配使用的View---
InternalResourceView
和JstlView
所謂專門的人做專門的事,如果你的項目中使用的是jsp,那麼你就必須使用這個InternalResourceViewResolver。
下面寫一個例子:@Controller @RequestMapping("/view") public class ViewController { @RequestMapping("/hellojsp") public String toHelloJsp(){ return "hello"; } }
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="order" value="10"></property> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> <!-- <property name="viewClass" ref="jstViewClass"></property> --> </bean>
jsp文件目錄: 瀏覽器請求http://localhost:8088/view/hellojsp
4. AbstractTemplateViewResolver
AbstractTemplateViewResolver是UrlBasedViewResolver的子類,看下官方說明:Abstract base class for template view resolvers,in particular for Velocity and FreeMarker views.
意思是:它是一個抽象類,主要用來生成模板view,比如Velocity和FreeMarker。這個類是一個抽象類,那麼自然不能實例化,所以要生成Velocity或者FreeMarker這些View,就自然而然需要相對應的子類來完成。所以我們下面直接來看他的子類。
5.FreeMarkerViewResolver
AbstractTemplateViewResolver的子類,如果想使用freemark這樣的模板作爲view來作爲請求的內容展示,那麼就必須使用這個resolver來生成對應的FreeMarker的View。也就是說如果你的頁面想使用FreeMarker模板來生成,那麼你就需要聲明一個FreeMarkerViewResolver。爲什麼必須使用FreeMarkerViewResolver才能來生成FreeMark的View呢,看一下源碼:public class FreeMarkerViewResolver extends AbstractTemplateViewResolver { public FreeMarkerViewResolver() { setViewClass(requiredViewClass()); } public FreeMarkerViewResolver(String prefix, String suffix) { this(); setPrefix(prefix); setSuffix(suffix); } @Override protected Class<?> requiredViewClass() { return FreeMarkerView.class; } }
看到requiredViewClass方法了吧,這裏生成了FreeMarkerView(前面說過,不同的resolver生成不同的View,就體現在這個方法上)。
下面通過一個使用FreeMarker顯示index頁面的例子來說明一下如果使用:
第一步:pom.xml中引入FreeMarker的jar
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency>
第二步:配置spring-mvc.xml,引入resolver,同時必須引入FreeMarkerConfigurer,否則會報異常
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath"> <value>/WEB-INF/flt/</value><!--所有freemark文件放到flt目錄下--> </property> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="order" value="10"></property> <property name="prefix" value=""></property> <property name="suffix" value=".ftl"></property> </bean>
前面說過FreeMarkerViewResolver也是UrlBasedViewResolver的子類,所有用法跟上面的jsp的配置一樣,只要配置prefix和suffix就行。原理上面已經說過了。
第三步:寫Controller@Controller @RequestMapping("/view") public class ViewController { @RequestMapping("/freemark") public String toFreemark(){ return "index"; } }
第四步:寫FreeMarker模板文件index.ftl:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!-- <meta http-equiv="refresh" content="0; url=hello.ftl" /> --> <title>Insert title here</title> </head> <body> <a href="helloFtl">hello</a> </body> </html>
第五步:測試,瀏覽器http://localhost:8087/view/freemark
-
GroovyMarkupViewResolver
這個Resolver同樣是UrlBasedViewResolver的子類,所有用法跟上面的jsp的配置一樣,因爲我們很少使用,所以這裏就做實例樣式了,可自行百度 -
VelocityViewResolver
這個Resolver同樣是UrlBasedViewResolver的子類,所有用法跟上面的jsp的配置一樣,因爲我們很少使用,所以這裏就做實例樣式了,可自行百度,並且官方已經經此類棄用了,如下圖,所以我們也不寫例子,主要指導器原理就行。 -
JasperReportsViewResolver
JasperReportsViewResolver主要使用來生成JasperReportsView的,關於這個view,可以看一下下面的圖。這裏先來稍微數一下什麼是JasperReports,他就是一個專門用來生成報表的東東。看上圖,這個生成的報表又可以以多種形式來展示,比如有html形式的 ,pdf形式的,xls形式的等等。一會我們會通過一個html的小例子來演示一下。
因爲這個類同樣是UrlBasedViewResolver的子類,所以原理都是相同的。【例子有時間會填充在這】 -
ScriptTemplateViewResolver
這個類要與ScriptTemplateConfigurer一起使用。【例子有時間會填充在這】 -
TilesViewResolver
-
XsltViewResolver