ViewResolver
springMVC的主流程我們可以大概將其概括為如下:
- 初始化
- HandlerMapping找到對(duì)應(yīng)handler
- HandlerAdapter執(zhí)行handler
- 通過ViewResolver找到viewName對(duì)應(yīng)的view
- 使用view.render進(jìn)行視圖渲染
前面已經(jīng)閱讀了HandlerMapping和HandlerAdapter部分的源碼薇宠,那接下來就是ViewResolver部分的源碼拐迁。
首先是看一下ViewResolver接口的源碼侯谁,
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
ViewResolver接口只有一個(gè)方法resolveViewName霉祸,也就是說ViewResolver的作用只有一個(gè)诚撵,就是將viewName解析成相應(yīng)的view牺六。
VelocityViewResolver
ViewResolver有許多不同的實(shí)現(xiàn)材诽,這次我選擇了閱讀velocity的實(shí)現(xiàn)底挫。
類圖
VelocityViewResolver類圖
執(zhí)行過程
我下面按照VelocityViewResolver的執(zhí)行順序,將有關(guān)的主要方法都整合起來脸侥,
// AbstractCachingViewResolver
// 這個(gè)方法主要是做了一個(gè)緩存的功能建邓。若已經(jīng)存在,則直接取出睁枕;否則調(diào)用createView創(chuàng)建視圖并將其緩存
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) { // 判斷是否使用緩存官边》惺郑可以通過設(shè)置cacheLimit屬性為0來關(guān)閉緩存功能
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) { // 此處的判斷可以減少同步對(duì)于性能的影響
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) { // 沒有緩存則創(chuàng)建view
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) { // 將新建的view添加進(jìn)緩存
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
// UrlBasedViewResolver
protected View createView(String viewName, Locale locale) throws Exception {
// 判斷是否可以處理viewName,此處與viewNames屬性有關(guān)注簿,若未設(shè)置則默認(rèn)可以
if (!canHandle(viewName, locale)) {
return null;
}
// 對(duì)于redirect:開頭的
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);
}
// 對(duì)于forward:開頭的
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// 對(duì)于一般的情況
return super.createView(viewName, locale);
}
// AbstractCachingViewResolver
// 調(diào)用loadView
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
// UrlBasedViewResolver
protected View loadView(String viewName, Locale locale) throws Exception {
// 根據(jù)viewName創(chuàng)建出相應(yīng)的view
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
// 此處會(huì)對(duì)創(chuàng)建出的view進(jìn)行檢查罐氨,判斷是否存在相應(yīng)資源
return (view.checkResource(locale) ? result : null);
}
// UrlBasedViewResolver
// 由下面AbstractTemplateViewResolver的buildView方法調(diào)用
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
/* 設(shè)置view相應(yīng)屬性 */
view.setUrl(getPrefix() + viewName + getSuffix()); // 將 前綴+viewName+后綴 拼接在一起構(gòu)成url
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
if (this.exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
return view;
}
// AbstractTemplateViewResolver
// 由下面VelocityViewResolver的buildView方法調(diào)用
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractTemplateView view = (AbstractTemplateView) super.buildView(viewName);
/* 設(shè)置相應(yīng)屬性 */
view.setExposeRequestAttributes(this.exposeRequestAttributes);
view.setAllowRequestOverride(this.allowRequestOverride);
view.setExposeSessionAttributes(this.exposeSessionAttributes);
view.setAllowSessionOverride(this.allowSessionOverride);
view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
return view;
}
// VelocityViewResolver
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
VelocityView view = (VelocityView) super.buildView(viewName);
/* 設(shè)置相應(yīng)屬性 */
view.setDateToolAttribute(this.dateToolAttribute);
view.setNumberToolAttribute(this.numberToolAttribute);
if (this.toolboxConfigLocation != null) {
((VelocityToolboxView) view).setToolboxConfigLocation(this.toolboxConfigLocation);
}
return view;
}
用文字分析一下這個(gè)流程,如下:
- 先在緩存中查找
- 通過canHandle方法來判斷是否解析
- 對(duì)于redirect和forward的方式進(jìn)行單獨(dú)處理
- 創(chuàng)建一個(gè)view實(shí)例滩援,對(duì)該view進(jìn)行各項(xiàng)屬性的設(shè)置栅隐,例如url,contentType等
- 調(diào)用view的checkResource方法判斷該view是否存在
- 將view存入緩存
總結(jié)
我們可以從源碼中知道下面一些比較有用的信息:
- ViewResolver有一個(gè)緩存機(jī)制
- 該緩存可以通過設(shè)置cacheLimit屬性來控制最大容量玩徊,若cacheLimit為0租悄,則不設(shè)置緩存
- 該緩存會(huì)對(duì)不存在的viewName也同樣進(jìn)行緩存的
- ViewResolver可以通過設(shè)置viewNames屬性來做viewName的攔截設(shè)置
- ViewResolver需要自己設(shè)置prefix和suffix屬性來確定資源,也就是如果后綴名設(shè)置成jsp恩袱,也同樣會(huì)去尋找jsp文件當(dāng)作velocity文件來解析
ViewResolver的緩存機(jī)制
寫著寫著泣棋,發(fā)現(xiàn)ViewResolver采用了很特別的緩存機(jī)制,ViewResolver采用了兩個(gè)緩存畔塔,其中第一個(gè)采用ConcurrentHashMap潭辈,第二個(gè)使用了LinkedHashMap。此處其實(shí)LinkedHashMap是為了做容量限制用的澈吨。因?yàn)镃oncurrentHashMap支持并發(fā)把敢,而LinkedHashMap支持容量限制,于是將通過LinkedHashMap的容量限制功能來實(shí)現(xiàn)ConcurrentHashMap的容量限制谅辣。