Spring MVC組件之ViewResolver
ViewResolver概述
ViewResolver組件的作用主要根據視圖名和本地化的Locale,來解析出相應的視圖View對象。
在SpringMvc的源碼中,ViewResolver是一個接口。該接口主要定義了一個方法。
? View resolveViewName(String viewName, Locale locale)
接口方法中需要viewName 和locale兩個參數(shù),viewName是視圖名稱,locale是本地化的參數(shù)。一般情況下,我們只需要通過viewName來找到相應的視圖。在國際化的場景中,可以根據不同的locale來展示不同語言的視圖。
ViewResolver類圖

從以上類圖中可以看出,AbstractCachingViewResolver,BeanNameViewResolver,ContentNegotiatingViewResolver,ViewResolverComposite這四個類直接繼承至 ViewResolver接口,都只有一層繼承結構。
只有AbstractCachingViewResolver類的繼承關系相對來說復雜一些,AbstractCachingViewResolver類可以緩存已經解析過的視圖。
AbstractCachingViewResolver系列
AbstractCachingViewResolver
AbstractCachingViewResolver類是一個抽象類。它提供了對View的緩存功能,當視圖解析過一次就被緩存起來, 直到緩存的視圖被刪除前,視圖解析都是自動從緩存中獲取的。
AbstractCachingViewResolver類繼承了WebApplicationObjectSupport父類,實現(xiàn)了ViewResolver接口。
AbstractCachingViewResolver類實現(xiàn)了ViewResolver接口的resolveViewName方法。
@Override
@Nullable
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);
}
}
resolveViewName方法中主要做了以下幾件事情。
- 首先要判斷是否開啟緩存。如果沒有開啟緩存,就直接調用createView(viewName, locale)方法,來創(chuàng)建一個視圖。
- 如果有開啟緩存,首先獲取視圖緩存的key值。然后再通過key值,在viewAccessCache緩存中獲取視圖。如果視圖存在就直接返回。
- 如果viewAccessCache緩存中不存在該視圖,則到viewCreationCache緩存中去找這個視圖,找到就返回該視圖。
- 如果viewCreationCache緩存中也不存在該視圖,則再調用createView(viewName, locale)方法來創(chuàng)建一個視圖返回。并把創(chuàng)建的視圖放到viewAccessCache和viewCreationCache的緩存中。
在createView方法中,其實又調用了loadView方法。loadView方法在AbstractCachingViewResolver類中只是一個抽象方法,專門留給子類來實現(xiàn)自身的業(yè)務邏輯。
AbstractCachingViewResolver類的緩存
在AbstractCachingViewResolver類中,使用了viewAccessCache和viewCreationCache兩個HashMap來做緩存。
viewAccessCache是ConcurrentHashMap的類型。ConcurrentHashMap是一個支持高并發(fā),線程安全,高效的哈希表。
viewCreationCache是LinkedHashMap類型的。LinkedHashMap是HashMap和雙向鏈表的組合,它通過維護一個雙向鏈表保證了值是有序的。LinkedHashMap中有一個removeEldestEntry方法。該方法返回一個boolearn值,當返回值為true的時候,LinkedHashMap會自動刪除最前面的一個Map值。該方法是在put和putAll方法被調用是被觸發(fā)。
在AbstractCachingViewResolver類中,是這樣定義viewCreationCache的。viewCreationCache的重寫了父類的removeEldestEntry方法。在該方法中會判斷視圖的數(shù)量是否超過了限制。如果已超過限制,就會先在viewAccessCache緩存刪除掉最先緩存的視圖。然后返回true, viewCreationCache就會制動刪除這個視圖。這樣就將兩種類型的map各自優(yōu)勢結合起來了。
@SuppressWarnings("serial")
private final Map<Object, View> viewCreationCache =
new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
if (size() > getCacheLimit()) {
viewAccessCache.remove(eldest.getKey());
return true;
}
else {
return false;
}
}
};
UrlBasedViewResolver
UrlBasedViewResolver直接繼承了AbstractCachingViewResolver類。它是對viewResolvers接口一個比較基本的實現(xiàn)。它提供了一種URL拼接的方式來解析視圖。
UrlBasedViewResolver類可以通過prefix屬性和suffix屬性,來分別設置一個前綴和后綴,最后視圖的URL就是前綴+邏輯視圖+后綴。
它主要重寫了父類的getCacheKey,createView,loadView三個方法。
? getCacheKey方法
@Override
protected Object getCacheKey(String viewName, Locale locale) {
return viewName;
}
getCacheKey方法直接返回viewName,舍棄了locale這個參數(shù)??梢奤rlBasedViewResolver類是不支持Locale。
? 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());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(REDIRECT_URL_PREFIX, 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);
}
createView方法主要做了以下四件事情。
- 在canHandle方法中,通過視圖名稱來檢查視圖是否能被解析。
- 判斷視圖名稱是否是以“redirect:”開頭,如果是,則創(chuàng)建一個RedirectView類型的視圖返回。RedirectView視圖是用于重定向跳轉。
- 再判斷視圖名稱是否是以“forward:”開頭,如果是,則創(chuàng)建一個InternalResourceView類型的forward視圖返回。forward視圖是用于轉發(fā)操作。
- 如果不是以上的3種情況,則調用父類的createView方法。父類的createView方法又會調用loadView方法。
? loadView方法
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
在loadView方法主要做了三件事情。
- 使用buildView方法創(chuàng)建一個視圖。在buildView方法中根據視圖的類型,使用了org.springframework.beans.BeanUtils.instantiateClass方法,創(chuàng)建了一個AbstractUrlBasedView類型的實例。AbstractUrlBasedView的實例創(chuàng)建好后,再對這個實例進行一些屬性的設置。
視圖中的url屬性就是在這里設置的,url就是前綴加視圖名再加后綴組合而成的。 - 使用applyLifecycleMethods方法,對創(chuàng)建的視圖進行初始化的設置。
- 判斷視圖模板是否存在,如存在則返回,不存在就返回NULL值。
UrlBasedViewResolver實現(xiàn)了視圖解析的基本功能,作為UrlBasedViewResolver類的子類只需要重寫父類的兩個方法,便可實現(xiàn)自己的功能。
? 重寫了父類的requiredViewClass方法,在這個方法中返回自己的類型。
? 類重寫了父類的buildView方法,先調用父類的buildView方法得到一個View對象后,再對該對設置自己獨有的屬性。
InternalResourceViewResolver
InternalResourceViewResolver類繼承至UrlBasedViewResolver類。Spring MVC容器在初始化ViewResolver類型的組件時,初始化的就有InternalResourceViewResolver組件。在實際應用中InternalResourceViewResolver也是使用的最廣泛的一個視圖解析器。InternalResourceViewResolver會把返回的視圖名稱都解析為InternalResourceView對象。
InternalResourceViewResolver類重寫了父類的requiredViewClass方法,返回的是InternalResourceView類型。
InternalResourceViewResolver類重寫了父類的buildView方法,在buildView方法中,先調用父類的方法構建一個InternalResourceView對象。再設置對象InternalResourceView的alwaysInclude,preventDispatchLoop的屬性值。
ViewResolverComposite
ViewResolverComposite類繼承至ViewResolver接口。從ViewResolverComposite類名稱可以知道它是一系列ViewResolver的組合。
ViewResolverComposite類中有一個屬性是viewResolvers。它是一個ViewResolver類型的集合。我們可以對這個屬性值進行設置。
ViewResolverComposite類實現(xiàn)了ViewResolver接口的resolveViewName方法。在resolveViewName方法中,程序會循環(huán)遍歷viewResolvers集合,針對集合中的每一個ViewResolver的實例。調用每一個實例的resolveViewName方法,直到得到一個View為止。代碼如下所示:
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}