概述

实体解析器接口,根据视图名和国际化,获得最终的视图 View 对象。代码如下:

1
2
3
4
5
6
7
8
9
10
11
// ViewResolver.java

public interface ViewResolver {

/**
* 根据视图名和国际化,获得最终的 View 对象
*/
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;

}

类图

初始化

我们以默认配置的 Spring Boot 场景下为例,来一起看看 DispatcherServlet 的 #initViewResolvers(ApplicationContext context) 方法,初始化 viewResolvers 变量。代码如下

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
// DispatcherServlet.java

/** List of ViewResolvers used by this servlet. */
@Nullable
private List<ViewResolver> viewResolvers;

/** Detect all ViewResolvers or just expect "viewResolver" bean?. */
private boolean detectAllViewResolvers = true;

private void initViewResolvers(ApplicationContext context) {
// 置空 viewResolvers 处理
this.viewResolvers = null;

// 情况一,自动扫描 ViewResolver 类型的 Bean 们
if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<>(matchingBeans.values());
// We keep ViewResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
// 情况二,获得名字为 VIEW_RESOLVER_BEAN_NAME 的 Bean 们
} else {
try {
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
} catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default ViewResolver later.
}
}

// Ensure we have at least one ViewResolver, by registering
// a default ViewResolver if no other resolvers are found.
// 情况三,如果未获得到,则获得默认配置的 ViewResolver 类
if (this.viewResolvers == null) {
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
  • 一共有三种情况,初始化 viewResolvers 属性。

  • 默认情况下,detectAllViewResolvers 为 true ,所以走情况一的逻辑,自动扫描 ViewResolver 类型的 Bean 们。在默认配置的 Spring Boot 场景下,viewResolvers 的结果是:

    • ContentNegotiatingViewResolver
    • BeanNameViewResolver
    • ThymeleafViewResolver
    • ViewResolverComposite
    • InternalResourceViewResolver

从实现上来说,ContentNegotiatingViewResolver 是最最最重要的 ViewResolver 实现类

4. ContentNegotiatingViewResolver

实现 ViewResolver、Ordered、InitializingBean 接口,继承 WebApplicationObjectSupport 抽象类,基于内容类型来获取对应 View 的 ViewResolver 实现类。

内容类型指的是 “Content-Type” 和拓展后缀。

构造方法

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
@Nullable
private ContentNegotiationManager contentNegotiationManager;
/**
* ContentNegotiationManager 的工厂,用于创建 {@link #contentNegotiationManager} 对象
*/
private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean();

/**
* 在找不到 View 对象时,返回 {@link #NOT_ACCEPTABLE_VIEW}
*/
private boolean useNotAcceptableStatusCode = false;

/**
* 默认 View 数组
*/
@Nullable
private List<View> defaultViews;

/**
* ViewResolver 数组
*/
@Nullable
private List<ViewResolver> viewResolvers;

/**
* 顺序,优先级最高
*/
private int order = Ordered.HIGHEST_PRECEDENCE;
  • viewResolvers 属性,ViewResolver 数组。

  • ContentNegotiatingViewResolver 会使用这些 viewResolvers 们,解析出所有的 View 们,然后基于内容类型来获取对应的 View 们。此时的 View 结果,可能是一个,可能是多个,所以需要比较获取到最优的 View 对象。

    • defaultViews 属性,默认 View 数组。
    • 在 viewResolvers 们解析出所有的 View 们的基础上,也会添加 defaultViews 到 View 结果中。
  • order 属性,顺序,优先级最高。

initServletContext

初始化 viewResolvers 属性

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
@Override
protected void initServletContext(ServletContext servletContext) {
// <1> 扫描所有 ViewResolver 的 Bean 们
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
// <1.1> 情况一,如果 viewResolvers 为空,则将 matchingBeans 作为 viewResolvers 。
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) { // 排除自己
this.viewResolvers.add(viewResolver);
}
}
// <1.2> 情况二,如果 viewResolvers 非空,则和 matchingBeans 进行比对,判断哪些未进行初始化,那么需要进行初始化
} else {
for (int i = 0; i < this.viewResolvers.size(); i++) {
ViewResolver vr = this.viewResolvers.get(i);
// 已存在在 matchingBeans 中,说明已经初始化,则直接 continue
if (matchingBeans.contains(vr)) {
continue;
}
// 不存在在 matchingBeans 中,说明还未初始化,则进行初始化
String name = vr.getClass().getName() + i;
obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
}
}
// <1.3> 排序 viewResolvers 数组
AnnotationAwareOrderComparator.sort(this.viewResolvers);

// <2> 设置 cnmFactoryBean 的 servletContext 属性
this.cnmFactoryBean.setServletContext(servletContext);
}
  • 扫描所有 ViewResolver 的 Bean 们。

    • 情况一,如果 viewResolvers 为空,则将 matchingBeans 作为 viewResolvers 。默认情况下,走的是这段逻辑。所以此时 viewResolvers 会有 BeanNameViewResolver、ThymeleafViewResolver、ViewResolverComposite、InternalResourceViewResolver 四个对象。
    • 情况二,如果 viewResolvers 非空,则和 matchingBeans 进行比对,判断哪些未进行初始化,那么需要进行初始化。
    • 排序 viewResolvers 数组
  • 设置 cnmFactoryBean 的 servletContext 属性

afterPropertiesSet

1
2
3
4
5
6
7
8
9
10
@Override
public void afterPropertiesSet() {
// 如果 contentNegotiationManager 为空,则进行创建
if (this.contentNegotiationManager == null) {
this.contentNegotiationManager = this.cnmFactoryBean.build();
}
if (this.viewResolvers == null || this.viewResolvers.isEmpty()) {
logger.warn("No ViewResolvers configured");
}
}

resolveViewName

实现 #resolveViewName(String viewName, Locale locale) 方法

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
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
// <1> 获得 MediaType 数组
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
// <2.1> 获得匹配的 View 数组
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
// <2.2> 筛选最匹配的 View 对象
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
// 如果筛选成功,则返回
if (bestView != null) {
return bestView;
}
}

String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";

// <3> 如果匹配不到 View 对象,则根据 useNotAcceptableStatusCode ,返回 NOT_ACCEPTABLE_VIEW 或 null 。
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
} else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
  • 调用 #getCandidateViews(HttpServletRequest request) 方法,获得 MediaType 数组
  • 调用 #getCandidateViews(String viewName, Locale locale, List requestedMediaTypes) 方法,获得匹配的 View 数组
  • 调用 #getBestView(List candidateViews, List requestedMediaTypes, RequestAttributes attrs) 方法,筛选最匹配的 View 对象。
  • 如果匹配不到 View 对象,则根据 useNotAcceptableStatusCode ,返回 NOT_ACCEPTABLE_VIEW 或 null 。其中,NOT_ACCEPTABLE_VIEW 变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final View NOT_ACCEPTABLE_VIEW = new View() {

@Override
@Nullable
public String getContentType() {
return null;
}

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
}

};

这个视图的渲染,只会设置响应状态码为 SC_NOT_ACCEPTABLE 。

getMediaTypes

获得 MediaType 数组

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
@Nullable
protected List<MediaType> getMediaTypes(HttpServletRequest request) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
try {
// 创建 ServletWebRequest 对象
ServletWebRequest webRequest = new ServletWebRequest(request);
// 从请求中,获得可接受的 MediaType 数组。默认实现是,从请求头 ACCEPT 中获取
List<MediaType> acceptableMediaTypes = this.contentNegotiationManager.resolveMediaTypes(webRequest);
// 获得可产生的 MediaType 数组
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request);
// 通过 acceptableTypes 来比对,将符合的 producibleType 添加到 mediaTypesToUse 结果数组中
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<>();
for (MediaType acceptable : acceptableMediaTypes) {
for (MediaType producible : producibleMediaTypes) {
if (acceptable.isCompatibleWith(producible)) {
compatibleMediaTypes.add(getMostSpecificMediaType(acceptable, producible));
}
}
}

// 按照 MediaType 的 specificity、quality 排序
List<MediaType> selectedMediaTypes = new ArrayList<>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(selectedMediaTypes);
return selectedMediaTypes;
} catch (HttpMediaTypeNotAcceptableException ex) {
if (logger.isDebugEnabled()) {
logger.debug(ex.getMessage());
}
return null;
}
}

@SuppressWarnings("unchecked")
private List<MediaType> getProducibleMediaTypes(HttpServletRequest request) {
Set<MediaType> mediaTypes = (Set<MediaType>)
request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
} else {
return Collections.singletonList(MediaType.ALL);
}
}

/**
* Return the more specific of the acceptable and the producible media types
* with the q-value of the former.
*/
private MediaType getMostSpecificMediaType(MediaType acceptType, MediaType produceType) {
produceType = produceType.copyQualityValue(acceptType);
return (MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceType) < 0 ? acceptType : produceType);
}

getCandidateViews

获得匹配的 View 数组

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
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
// 创建 View 数组
List<View> candidateViews = new ArrayList<>();

// <1> 来源一,通过 viewResolvers 解析出 View 数组结果,添加到 candidateViews 中
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
// <1.1> 遍历 viewResolvers 数组
for (ViewResolver viewResolver : this.viewResolvers) {
// <1.2> 情况��,获得 View 对象,添加到 candidateViews 中
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
// <1.3> 情况二,带有文拓展后缀的方式,获得 View 对象,添加到 candidateViews 中
// <1.3.1> 遍历 MediaType 数组
for (MediaType requestedMediaType : requestedMediaTypes) {
// <1.3.2> 获得 MediaType 对应的拓展后缀的数组
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
// <1.3.3> 遍历拓展后缀的数组
for (String extension : extensions) {
// <1.3.4> 带有文拓展后缀的方式,获得 View 对象,添加到 candidateViews 中
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}

// <2> 来源二,添加 defaultViews 到 candidateViews 中
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
  • 一共有两个来源

    • 来源一,通过 viewResolvers 解析出 View 数组结果,添加到 candidateViews 中

      • 遍历 viewResolvers 数组

      • 获得 View 对象,添加到 candidateViews 中

      • 带有文拓展后缀的方式,获得 View 对象,添加到 candidateViews 中。

        • 遍历 MediaType 数组
        • 获得 MediaType 对应的拓展后缀的数组。
        • 遍历拓展后缀的数组
        • 带有文拓展后缀的方式,获得 View 对象,添加到 candidateViews 中
    • 添加 defaultViews 到 candidateViews 中。

getBestView

筛选最匹配的 View 对象

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
@Nullable
private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs) {
// <1> 遍历 candidateView 数组,如果有重定向的 View 类型,则返回它
for (View candidateView : candidateViews) {
if (candidateView instanceof SmartView) {
SmartView smartView = (SmartView) candidateView; // RedirectView 是 SmartView 的子类
if (smartView.isRedirectView()) {
return candidateView;
}
}
}
// <2> 遍历 requestedMediaTypes 数组
for (MediaType mediaType : requestedMediaTypes) {
// <2> 遍历 candidateViews 数组
for (View candidateView : candidateViews) {
if (StringUtils.hasText(candidateView.getContentType())) {
// <2.1> 如果 MediaType 类型匹配,则返回该 View 对象
MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType());
if (mediaType.isCompatibleWith(candidateContentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Selected '" + mediaType + "' given " + requestedMediaTypes);
}
attrs.setAttribute(View.SELECTED_CONTENT_TYPE, mediaType, RequestAttributes.SCOPE_REQUEST); // 设置匹配的 MediaType 到请求属性中
return candidateView;
}
}
}
}
return null;
}
  • 遍历 candidateView 数组,如果有重定向的 View 类型,则返回它。

    • 重定向的 View ,优先级更高。
  • 遍历 requestedMediaTypes 和 candidateViews 数组,先找到一个 MediaType 类型匹配,则返回该 View 对象,然后返回它。

    • 优先级的匹配规则,由 ViewResolver 在 viewResolvers 的位置,越靠前,优先级越高。

BeanNameViewResolver

实现 ViewResolver、Ordered 接口,继承 WebApplicationObjectSupport 抽象类,基于 Bean 的名字获得 View 对象的 ViewResolver 实现类。

构造方法

1
2
3
4
/**
* 顺序,优先级最低
*/
private int order = Ordered.LOWEST_PRECEDENCE;

resolveViewName

获得 Bean 的名字获得 View 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws BeansException {
// 如果 Bean 对应的 Bean 对象不存在,则返回 null
ApplicationContext context = obtainApplicationContext();
if (!context.containsBean(viewName)) {
// Allow for ViewResolver chaining...
return null;
}
// 如果 Bean 对应的 Bean 类型不是 View ,则返回 null
if (!context.isTypeMatch(viewName, View.class)) {
if (logger.isDebugEnabled()) {
logger.debug("Found bean named '" + viewName + "' but it does not implement View");
}
// Since we're looking into the general ApplicationContext here,
// let's accept this as a non-match and allow for chaining as well...
return null;
}
// 获得 Bean 名字对应的 View 对象
return context.getBean(viewName, View.class);
}

ViewResolverComposite

实现 ViewResolver、Ordered、InitializingBean、ApplicationContextAware、ServletContextAware 接口,复合的 ViewResolver 实现类。

构造方法

1
2
3
4
5
6
7
8
9
/**
* ViewResolver 数组
*/
private final List<ViewResolver> viewResolvers = new ArrayList<>();

/**
* 顺序,优先级最低
*/
private int order = Ordered.LOWEST_PRECEDENCE;

afterPropertiesSet

1
2
3
4
5
6
7
8
9
10
// ViewResolverComposite.java

@Override
public void afterPropertiesSet() throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
if (viewResolver instanceof InitializingBean) {
((InitializingBean) viewResolver).afterPropertiesSet();
}
}
}

resolveViewName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ViewResolverComposite.java

@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 遍历 viewResolvers 数组,逐个进行解析,但凡成功,则返回该 View 对象
for (ViewResolver viewResolver : this.viewResolvers) {
// 执行解析
View view = viewResolver.resolveViewName(viewName, locale);
// 解析成功,则返回该 View 对象
if (view != null) {
return view;
}
}
return null;
}

AbstractCachingViewResolver

实现 ViewResolver 接口,继承 WebApplicationObjectSupport 抽象类,提供通用的缓存的 ViewResolver 抽象类。对于相同的视图名,返回的是相同的 View 对象,所以通过缓存,可以进一步提供性能。

构造方法

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
/**
* Default maximum number of entries for the view cache: 1024.
*/
public static final int DEFAULT_CACHE_LIMIT = 1024;

/**
* Dummy marker object for unresolved views in the cache Maps.
*/
private static final View UNRESOLVED_VIEW = new View() {

@Override
@Nullable
public String getContentType() {
return null;
}

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
}

};

/**
* The maximum number of entries in the cache.
*/
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; // 缓存上限。如果 cacheLimit = 0 ,表示禁用缓存

/**
* Whether we should refrain from resolving views again if unresolved once.
*/
private boolean cacheUnresolved = true; // 是否缓存空 View 对象

/**
* Fast access cache for Views, returning already cached instances without a global lock.
*
* View 的缓存的映射
*
* KEY:{@link #getCacheKey(String, Locale)}
*/
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT);

/**
* Map from view key to View instance, synchronized for View creation.
*
* View 的缓存的映射。相比 {@link #viewAccessCache} 来说,增加了 synchronized 锁
*/
@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) {
// 如果超过上限,则从 viewAccessCache 中也移除
if (size() > getCacheLimit()) {
viewAccessCache.remove(eldest.getKey());
return true;
} else {
return false;
}
}
};
  • 比较有趣的是 viewAccessCache 和 viewCreationCache 属性的存在

    • 通过 viewAccessCache 属性,提供更快的访问 View 缓存。
    • 通过 viewCreationCache 属性,提供缓存的上限的功能。可能有胖友不太了解为什么 LinkedHashMap 能实现 LRU 缓存过期的功能
    • KEY 是通过 #getCacheKey(String viewName, Locale locale) 方法,获得缓存 KEY 。
1
2
3
4
5
6
7
8
9
10
/**
* Return the cache key for the given view name and the given locale.
* <p>Default is a String consisting of view name and locale suffix.
* Can be overridden in subclasses.
* <p>Needs to respect the locale in general, as a different locale can
* lead to a different view resource.
*/
protected Object getCacheKey(String viewName, Locale locale) {
return viewName + '_' + locale;
}

loadView

抽象方法,加载 viewName 对应的 View 对象

1
2
@Nullable
protected abstract View loadView(String viewName, Locale locale) throws Exception;

createView

创建 viewName 对应的 View 对象

1
2
3
4
@Nullable
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}

resolveViewName

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
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 如果禁用缓存,则创建 viewName 对应的 View 对象
if (!isCache()) {
return createView(viewName, locale);
} else {
// 获得缓存 KEY
Object cacheKey = getCacheKey(viewName, locale);
// 从 viewAccessCache 缓存中,获得 View 对象
View view = this.viewAccessCache.get(cacheKey);
// 如果获得不到缓存,则从 viewCreationCache 中,获得 View 对象
if (view == null) {
// synchronized 锁
synchronized (this.viewCreationCache) {
// 从 viewCreationCache 中,获得 View 对象
view = this.viewCreationCache.get(cacheKey);
// 如果不存在,则创建 viewName 对应的 View 对象
if (view == null) {
// Ask the subclass to create the View object.
// 创建 viewName 对应的 View 对象
view = createView(viewName, locale);
// 如果创建失败,但是 cacheUnresolved 为 true ,则设置为 UNRESOLVED_VIEW
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
// 如果 view 非空,则添加到 viewAccessCache 缓存中
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
} else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
// 返回 view
return (view != UNRESOLVED_VIEW ? view : null);
}
}

子类

有三个子类:

  • UrlBasedViewResolver
  • XmlViewResolver
  • ResourceBundleViewResolver

其中,UrlBasedViewResolver 是相比更关键的子类

UrlBasedViewResolver

实现 Ordered 接口,继承 AbstractCachingViewResolver 抽象类,基于 Url 的 ViewResolver 实现类。

构造方法

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
// UrlBasedViewResolver.java

/**
* Prefix for special view names that specify a redirect URL (usually
* to a controller after a form has been submitted and processed).
* Such view names will not be resolved in the configured default
* way but rather be treated as special shortcut.
*/
public static final String REDIRECT_URL_PREFIX = "redirect:";

/**
* Prefix for special view names that specify a forward URL (usually
* to a controller after a form has been submitted and processed).
* Such view names will not be resolved in the configured default
* way but rather be treated as special shortcut.
*/
public static final String FORWARD_URL_PREFIX = "forward:";

/**
* View 的类型
*
* 不同的实现类,会对应一个 View 的类型
*/
@Nullable
private Class<?> viewClass;

/**
* 前缀
*/
private String prefix = "";
/**
* 后缀
*/
private String suffix = "";

/**
* ContentType 类型
*/
@Nullable
private String contentType;

/**
*
*/
private boolean redirectContextRelative = true;

private boolean redirectHttp10Compatible = true;

@Nullable
private String[] redirectHosts;

/**
* RequestAttributes 暴露给 View 使用时的属性
*/
@Nullable
private String requestContextAttribute;

@Nullable
private Boolean exposeContextBeansAsAttributes;

/** Map of static attributes, keyed by attribute name (String). */
private final Map<String, Object> staticAttributes = new HashMap<>();

/**
* 是否暴露路径变量给 View 使用
*/
@Nullable
private Boolean exposePathVariables;

@Nullable
private String[] exposedContextBeanNames;

/**
* 是否只处理指定的视图名们
*/
@Nullable
private String[] viewNames;

/**
* 顺序,优先级最低
*/
private int order = Ordered.LOWEST_PRECEDENCE;

initApplicationContext

1
2
3
4
5
6
7
8
9
@Override
protected void initApplicationContext() {
// 调用父类该方法,进行初始化
super.initApplicationContext();
// 校验 viewClass 非空
if (getViewClass() == null) {
throw new IllegalArgumentException("Property 'viewClass' is required");
}
}

getCacheKey

忽略 locale 参数,仅使用 viewName 作为缓存 KEY 。

1
2
3
4
@Override
protected Object getCacheKey(String viewName, Locale locale) {
return viewName;
}

canHandle

判断传入的视图名是否可以被处理

1
2
3
4
protected boolean canHandle(String viewName, Locale locale) {
String[] viewNames = getViewNames();
return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName));
}

一般情况下,viewNames 为空,所以会满足 viewNames == null 代码块。也就说,所有视图名都可以被处理。

applyLifecycleMethods

1
2
3
4
5
6
7
8
9
10
11
12
protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
// 情况一,如果 viewName 有对应的 View Bean 对象,则使用它
ApplicationContext context = getApplicationContext();
if (context != null) {
Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
if (initialized instanceof View) {
return (View) initialized;
}
}
// 情况二,直接返回 view
return view;
}

createView

加了对 REDIRECT、FORWARD 的情况的处理。代码如下:

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
@Override
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.
// 如果是 REDIRECT 开头,创建 RedirectView 视图
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
// 创建 RedirectView 对象
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl,
isRedirectContextRelative(), isRedirectHttp10Compatible());
// 设置 RedirectView 对象的 hosts 属性
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
// 应用
return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
}

// Check for special "forward:" prefix.
// 如果是 FORWARD 开头,创建 InternalResourceView 视图
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
// 创建 InternalResourceView 对象
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
InternalResourceView view = new InternalResourceView(forwardUrl);
// 应用
return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
}

// Else fall back to superclass implementation: calling loadView.
// 创建视图名对应的 View 对象
return super.createView(viewName, locale);
}

loadView

加载 viewName 对应的 View 对象

1
2
3
4
5
6
7
8
9
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
// <x> 创建 viewName 对应的 View 对象
AbstractUrlBasedView view = buildView(viewName);
// 应用
View result = applyLifecycleMethods(viewName, view);
// 返回
return (view.checkResource(locale) ? result : null);
}

buildView(String viewName) 方法,创建 viewName 对应的 View 对象

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
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");

// 创建 AbstractUrlBasedView 对象
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);

// 设置各种属性

view.setUrl(getPrefix() + viewName + getSuffix());

String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}

view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());

Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}

return view;
}

requiredViewClass

定义了产生的视图

1
2
3
protected Class<?> requiredViewClass() {
return AbstractUrlBasedView.class;
}

子类

  • AbstractTemplateViewResolver
  • InternalResourceViewResolver
  • TilesViewResolver
  • ScriptTemplateViewResolver
  • XsltViewResolver

其中,InternalResourceViewResolver 和 AbstractTemplateViewResolver 是相比更关键的子类

InternalResourceViewResolver

继承 UrlBasedViewResolver 类,解析出 JSP 的 ViewResolver 实现类。

构造方法

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
/**
* 判断 javax.servlet.jsp.jstl.core.Config 是否存在
*/
private static final boolean jstlPresent = ClassUtils.isPresent(
"javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());

@Nullable
private Boolean alwaysInclude;

public InternalResourceViewResolver() {
// 获得 viewClass
Class<?> viewClass = requiredViewClass();
if (InternalResourceView.class == viewClass && jstlPresent) {
viewClass = JstlView.class;
}
// 设置 viewClass
setViewClass(viewClass);
}

public InternalResourceViewResolver(String prefix, String suffix) {
this();
setPrefix(prefix);
setSuffix(suffix);
}

@Override
protected Class<?> requiredViewClass() {
return InternalResourceView.class;
}

从构造方法中,可以看出,视图名会是 InternalResourceView 或 JstlView 类。 实际上,JstlView 是 InternalResourceView 的子类。

buildView

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
// 调用父方法
InternalResourceView view = (InternalResourceView) super.buildView(viewName);

// 设置 View 对象的相关属性
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
view.setPreventDispatchLoop(true);
return view;
}

增加设置两个属性

AbstractTemplateViewResolver

继承 UrlBasedViewResolver 类,解析出 AbstractTemplateView 的 ViewResolver 抽象类。

构造方法

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
// AbstractTemplateViewResolver.java

/**
* 是否将所有 RequestAttributes 暴露给 View 使用
*/
private boolean exposeRequestAttributes = false;
/**
* 当 RequestAttributes 中存在 Model 中同名的参数,是否允许使用 RequestAttributes 中的值将 Model 中的值进行覆盖
*
* {@link #exposeRequestAttributes}
*/
private boolean allowRequestOverride = false;

/**
* 是否将 SessionAttributes 暴露给 View 使用
*/
private boolean exposeSessionAttributes = false;
/**
* 当 SessionAttributes 中存在 Model 中同名的参数,是否允许使用 SessionAttributes 中的值将 Model 中的值进行覆盖
*
* {@link #exposeSessionAttributes}
*/
private boolean allowSessionOverride = false;

/**
* 是否将 RequestContext 暴露给 view 为 spring 的宏( Macro )所使用
*/
private boolean exposeSpringMacroHelpers = true;

requiredViewClass

1
2
3
4
@Override
protected Class<?> requiredViewClass() {
return AbstractTemplateView.class;
}

buildView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// AbstractTemplateViewResolver.java

@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
// 调用父类方法,创建 AbstractTemplateView 对象
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;
}

增加设置五个属性

子类

有二个子类:

FreeMarkerViewResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// FreeMarkerViewResolver.java

public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {

public FreeMarkerViewResolver() {
setViewClass(requiredViewClass());
}

public FreeMarkerViewResolver(String prefix, String suffix) {
this();
setPrefix(prefix);
setSuffix(suffix);
}

/**
* Requires {@link FreeMarkerView}.
*/
@Override
protected Class<?> requiredViewClass() {
return FreeMarkerView.class;
}

}

GroovyMarkupViewResolver

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
// GroovyMarkupViewResolver.java

public class GroovyMarkupViewResolver extends AbstractTemplateViewResolver {

public GroovyMarkupViewResolver() {
setViewClass(requiredViewClass());
}

public GroovyMarkupViewResolver(String prefix, String suffix) {
this();
setPrefix(prefix);
setSuffix(suffix);
}


@Override
protected Class<?> requiredViewClass() {
return GroovyMarkupView.class;
}

/**
* This resolver supports i18n, so cache keys should contain the locale.
*/
@Override
protected Object getCacheKey(String viewName, Locale locale) {
return viewName + '_' + locale;
}

}