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。