ViewResolver
springMVC的主流程我们可以大概将其概括为如下:
- 初始化
- HandlerMapping找到对应handler
- HandlerAdapter执行handler
- 通过ViewResolver找到viewName对应的view
- 使用view.render进行视图渲染
前面已经阅读了HandlerMapping和HandlerAdapter部分的源码,那接下来就是ViewResolver部分的源码。
首先是看一下ViewResolver接口的源码,
1 2 3
| public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; }
|
ViewResolver接口只有一个方法resolveViewName,也就是说ViewResolver的作用只有一个,就是将viewName解析成相应的view。
VelocityViewResolver
ViewResolver有许多不同的实现,这次我选择了阅读velocity的实现。
类图
执行过程
我下面按照VelocityViewResolver的执行顺序,将有关的主要方法都整合起来,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
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) { 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); } }
protected View createView(String viewName, Locale locale) throws Exception { if (!canHandle(viewName, locale)) { return null; } 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); } if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView(forwardUrl); } return super.createView(viewName, locale); }
protected View createView(String viewName, Locale locale) throws Exception { return loadView(viewName, locale); }
protected View loadView(String viewName, Locale locale) throws Exception { AbstractUrlBasedView view = buildView(viewName); View result = applyLifecycleMethods(viewName, view); return (view.checkResource(locale) ? result : null); }
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; }
protected AbstractUrlBasedView buildView(String viewName) throws Exception { AbstractTemplateView view = (AbstractTemplateView) super.buildView(viewName); view.setExposeRequestAttributes(this.exposeRequestAttributes); view.setAllowRequestOverride(this.allowRequestOverride); view.setExposeSessionAttributes(this.exposeSessionAttributes); view.setAllowSessionOverride(this.allowSessionOverride); view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers); return view; }
protected AbstractUrlBasedView buildView(String viewName) throws Exception { VelocityView view = (VelocityView) super.buildView(viewName); view.setDateToolAttribute(this.dateToolAttribute); view.setNumberToolAttribute(this.numberToolAttribute); if (this.toolboxConfigLocation != null) { ((VelocityToolboxView) view).setToolboxConfigLocation(this.toolboxConfigLocation); } return view; }
|
用文字分析一下这个流程,如下:
- 先在缓存中查找
- 通过canHandle方法来判断是否解析
- 对于redirect和forward的方式进行单独处理
- 创建一个view实例,对该view进行各项属性的设置,例如url,contentType等
- 调用view的checkResource方法判断该view是否存在
- 将view存入缓存
总结
我们可以从源码中知道下面一些比较有用的信息:
- ViewResolver有一个缓存机制
- 该缓存可以通过设置cacheLimit属性来控制最大容量,若cacheLimit为0,则不设置缓存
- 该缓存会对不存在的viewName也同样进行缓存的
- ViewResolver可以通过设置viewNames属性来做viewName的拦截设置
- ViewResolver需要自己设置prefix和suffix属性来确定资源,也就是如果后缀名设置成jsp,也同样会去寻找jsp文件当作velocity文件来解析
ViewResolver的缓存机制
写着写着,发现ViewResolver采用了很特别的缓存机制,ViewResolver采用了两个缓存,其中第一个采用ConcurrentHashMap,第二个使用了LinkedHashMap。此处其实LinkedHashMap是为了做容量限制用的。因为ConcurrentHashMap支持并发,而LinkedHashMap支持容量限制,于是将通过LinkedHashMap的容量限制功能来实现ConcurrentHashMap的容量限制。