深入SpringMVC视图解析器
ViewResolver的主要职责是根据Controller所返回的ModelAndView中的逻辑视图名,为DispatcherServlet返回一个可用的View实例。SpringMVC中用于把View对象呈现给客户端的是View对象本身,而ViewResolver只是把逻辑视图名称解析为对象的View对象。因此,通常在接口层中,只需要返回一个view名称的字符串,ViewResolver就可以自动将字符串名称映射成对应的view进行输出。
首先,我们先看看ViewResolver的顶层抽象接口:
1 public interface ViewResolver { 2 3 /** 4 * Resolve the given view by name. 5 */ 6 View resolveViewName(String viewName, Locale locale) throws Exception; 7 8 }
其中,参数viewName表示待解析的视图名称,参数locale表示解析视图的区域,如果要实现国际化,就要注意该参数。
再看一下这个公共接口有哪些实现:
(1)AbstractCachingViewResolver是一个抽象类,这种视图解析器会把它曾经解析过的视图保存起来,然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的map中,接着再把新建的视图返回。使用这种视图缓存的方式可以把解析视图的性能问题降到最低。可以查看下其源码:
1 //默认的缓存大小 2 public static final int DEFAULT_CACHE_LIMIT = 1024; 3 4 //标志实际缓存大小 5 private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; 6 7 //缓存页面存放的集合,可以不加锁的获取已经缓存的views,主要用于高并发访问。 8 private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT); 9 10 //存放已经创建的views集合,主要用来统计淘汰策略 11 private final Map<Object, View> viewCreationCache = 12 new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) { 13 @Override 14 protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) { 15 if (size() > getCacheLimit()) { 16 viewAccessCache.remove(eldest.getKey()); 17 return true; 18 } 19 else { 20 return false; 21 } 22 } 23 }; 24 25 26 //实现接口的方法 27 @Override 28 public View resolveViewName(String viewName, Locale locale) throws Exception { 29 //没有开启缓存,创建views返回 30 if (!isCache()) { 31 return createView(viewName, locale); 32 } 33 else {//先从viewAccessCache中获取缓存 34 Object cacheKey = getCacheKey(viewName, locale); 35 View view = this.viewAccessCache.get(cacheKey); 36 //viewAccessCache中没有,去viewCreationCache中获取 37 if (view == null) { // 38 synchronized (this.viewCreationCache) { 39 view = this.viewCreationCache.get(cacheKey); 40 if (view == null) { 41 // 首先让子类去创建view 42 view = createView(viewName, locale); 43 if (view == null && this.cacheUnresolved) { 44 view = UNRESOLVED_VIEW; 45 } 46 //缓存创建的view 47 if (view != null) { 48 this.viewAccessCache.put(cacheKey, view); 49 this.viewCreationCache.put(cacheKey, view); 50 51 } 52 } 53 } 54 } 55 } 56 //存在缓存,直接返回 57 return (view != UNRESOLVED_VIEW ? view : null); 58 } 59 }
至于这里为什么使用viewAccessCache和viewCreationCache两个缓存,viewAccessCache可以在不添加锁的情况下实现高并发获取已经缓存的views,后者存储的views和前者一样,方便用来制定缓存淘汰策略。
(2)UrlBasedViewResolver是对ViewResolver的一种简单实现,而且继承了AbstractCachingViewResolver,主要就是提供的一种拼接URL的方式来解析视图,它可以让我们通过prefix属性指定一个指定的前缀,通过suffix属性指定一个指定的后缀,然后把返回的逻辑视图名称加上指定的前缀和后缀就是指定的视图URL。