1、ViewResolver简介
ViewResolver,是视图解析器,它主要的作用是根据视图名和Locale解析出对应的视图。ViewResolver视图解析器的类图结构,如下所示:
通过上面类图,我们知道ViewResolver家族中,直接实现ViewResolver接口的类一共有四个,其中三个都只有一个实现类,而AbstractCachingViewResolver类则是有一个庞大的分支。我们下面,分别分析四类视图解析器的实现。
ViewResolver接口
ViewResolver接口非常简单,只定义了一个根据viewName和locale解析视图的方法resolveViewName()。在实际的实现类中,有些用到了本地化Locale实例参数,有些则是忽略了该参数,后面再具体分析。
public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
2、BeanNameViewResolver类
BeanNameViewResolver是根据ViewName从ApplicationContext容器中查找相应的bean做View的实现类。具体实现如下:
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
//定义该视图解析器级别,order越小优先级越高
private int order = Ordered.LOWEST_PRECEDENCE;
//省略order的 get/set方法
//解析视图的方法实现,从当前容器中获取指定名称的解析器,如果没有对应的解析器,就交给其他解析器处理
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = obtainApplicationContext();
if (!context.containsBean(viewName)) {
// Allow for ViewResolver chaining...
return 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;
}
return context.getBean(viewName, View.class);
}
}
3、ViewResolverComposite类
解析器是一个特殊的视图解析器,类似前面提到的HandlerMethodArgumentResolverComposite参数解析器,它不实际解析任何视图,而是将多个别的视图解析器包含在其中(即存储在viewResolvers属性中),解析时调用其所包含的视图解析器具体解析相应的视图。
ViewResolverComposite视图解析器实现了InitializingBean、ApplicationContextAware、ServletContextAware 等接口,所以该视图解析器除了利用其他解析器实现视图解析功能,还提供了一些初始化操作。
因为实现了ApplicationContextAware接口,所以初始化时,会为了viewResolvers集合中实现了ApplicationContextAware接口的解析器设置ApplicationContext。同理,也会为了实现ServletContextAware接口的解析器设置ServletContext。同时,因为实现了InitializingBean接口,所以在初始化Bean的时候,会调用viewResolvers集合中所有实现了该接口的解析器的afterPropertiesSet()方法进行初始化配置的调用。具体实现如下:
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
for (ViewResolver viewResolver : this.viewResolvers) {
if (viewResolver instanceof ApplicationContextAware) {
((ApplicationContextAware)viewResolver).setApplicationContext(applicationContext);
}
}
}
@Override
public void setServletContext(ServletContext servletContext) {
for (ViewResolver viewResolver : this.viewResolvers) {
if (viewResolver instanceof ServletContextAware) {
((ServletContextAware)viewResolver).setServletContext(servletContext);
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
if (viewResolver instanceof InitializingBean) {
((InitializingBean) viewResolver).afterPropertiesSet();
}
}
}
实际的视图解析功能,主要交由viewResolvers集合中的视图解析器进行解析,具体实现如下:
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
4、ContentNegotiatingViewResolver类
ContentNegotiatingViewResolver视图解析器主要是在别的解析器解析的结果上增加了对媒体类型MediaType和后缀的支持。大致流程如下:首先根据内置的viewResolvers集合进行视图解析,可能发现多个适合的视图,然后再根据媒体类型MediaType和后缀进行判断,选择最优的视图进行使用。
字段属性
为了实现对媒体类型和后缀的支持,需要用到contentNegotiationManager和cnmFactoryBean两个属性,为了使用真正的视图解析器,定义了viewResolvers集合属性等。具体如下:
//判断媒体类型的工具类
@Nullable
private ContentNegotiationManager contentNegotiationManager;
//创建ContentNegotiationManager的工厂类
private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean();
//是否启用状态码406
private boolean useNotAcceptableStatusCode = false;
//默认的视图解析器
@Nullable
private List<View> defaultViews;
//视图解析器集合
@Nullable
private List<ViewResolver> viewResolvers;
//视图优先级定义
private int order = Ordered.HIGHEST_PRECEDENCE;
// get/set相关方法省略
属性初始化
ContentNegotiatingViewResolver类实现了Ordered、 InitializingBean接口,同时继承了WebApplicationObjectSupport类(直接或间接的实现了ApplicationContextAware和ServletContextAware接口),所以该类就有了几个进行初始化的方法,如下所示:
- initServletContext()初始化方法
主要实现了视图解析器集合viewResolvers属性的初始化工作。
@Override
protected void initServletContext(ServletContext servletContext) {
//查询当前容器,所有的ViewResolver类型的Bean
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
//当viewResolvers为null时,把出来自身之外的其他ViewResolver类型的Bean都添加到属性viewResolvers中
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
else {
//把viewResolvers集合中,没有进行实例化的解析器,进行实例化操作
for (int i = 0; i < this.viewResolvers.size(); i++) {
ViewResolver vr = this.viewResolvers.get(i);
if (matchingBeans.contains(vr)) {
continue;
}
String name = vr.getClass().getName() + i;
obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
}
}
//排序
AnnotationAwareOrderComparator.sort(this.viewResolvers);
//为cnmFactoryBean设置servletContext
this.cnmFactoryBean.setServletContext(servletContext);
}
- afterPropertiesSet()初始化方法
主要完成contentNegotiationManager属性的初始化,由cnmFactoryBean实例创建。
@Override
public void afterPropertiesSet() {
if (this.contentNegotiationManager == null) {
this.contentNegotiationManager = this.cnmFactoryBean.build();
}
if (this.viewResolvers == null || this.viewResolvers.isEmpty()) {
logger.warn("No ViewResolvers configured");
}
}
视图解析
在视图解析过程中,首先由视图解析器集合中的解析器去解析视图,这个时候可能产生多个满足要求的视图,然后再根据request获取包含的媒体信息,最后根据媒体信息和前面已经查询到的候选视图,然后由getBestView()方法去判断最合适的视图。
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//查询当前请求中的媒体类型,使用contentNegotiationManager工具类实现,不再具体分析
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
//查询候选视图,通过视图解析器集合实现,其中包括了对扩展名的处理,这里不再详细分析
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
//判断最佳视图,并返回(不等于null时)
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
//日志信息处理
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
//如果启用状态码406,当视图为空时,返回NOT_ACCEPTABLE_VIEW实例
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;
}
}
在视图解析过程中,如何判断最佳视图呢?我们来看一下具体的代码实现:
@Nullable
private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs) {
//判断是否是RedirectView类型
for (View candidateView : candidateViews) {
if (candidateView instanceof SmartView) {
SmartView smartView = (SmartView) candidateView;
if (smartView.isRedirectView()) {
return candidateView;
}
}
}
//当候选解析器中的ContentType类型与要求的MediaType匹配,则认为是最佳的解析器
for (MediaType mediaType : requestedMediaTypes) {
for (View candidateView : candidateViews) {
if (StringUtils.hasText(candidateView.getContentType())) {
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);
return candidateView;
}
}
}
}
return null;
}
5、InternalResourceViewResolver类
在AbstractCachingViewResolver系列视图解析器中,AbstractCachingViewResolver抽象类提供了统一的缓存功能,当视图解析过一次就被缓存起来。该抽象类有三个直接的实现类,分别是:ResourceBundleViewResolver、XmlViewResolver和UrlBasedViewResolver。其中,
- ResourceBundleViewResolver,通过使用properties属性配置文件解析视图的;
- XmlViewResolver,通过XML文件中的视图bean来解析“逻辑视图”,默认会从/WEB-INF/views.xml中加载视图bean;
- UrlBasedViewResolver,基于URL查找模板文件的视图解析器基类,设置了统一的查找模板的规则。
在AbstractCachingViewResolver系列视图解析器中,我们以InternalResourceViewResolver为例,分析视图解析器的实现方式。InternalResourceViewResolver类继承了UrlBasedViewResolver类,UrlBasedViewResolver类又继承了AbstractCachingViewResolver类,AbstractCachingViewResolver实现了视图解析器接口ViewResolver。
5.1、AbstractCachingViewResolver抽象类
AbstractCachingViewResolver抽象类主要提供了统一的缓存功能,当视图解析过一次就被缓存起来,后续都会直接从缓存中查询视图,提高了视图的查询效率。
字段属性
//默认缓存的最大个数
public static final int DEFAULT_CACHE_LIMIT = 1024;
//不可解析视图的标志
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) {
}
};
//是否缓存的过滤器接口的实现,对应内部接口CacheFilter
private static final CacheFilter DEFAULT_CACHE_FILTER = (view, viewName, locale) -> true;
//缓存数量限制,默认1024
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
//未解析时,是不是限制再次解析
private boolean cacheUnresolved = true;
//是否缓存的默认实现类
private CacheFilter cacheFilter = DEFAULT_CACHE_FILTER;
//View视图缓存,使用ConcurrentHashMap避免全局锁,实现快速查询
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT);
//主要提供了限制缓存最大数的功能,同步创建视图,避免超过最大值。创建时,操作最大时,同时删除两个Map对象中的值
@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) {
if (size() > getCacheLimit()) {
viewAccessCache.remove(eldest.getKey());
return true;
}
else {
return false;
}
}
};
视图解析
实现接口中解析视图的方法,通过调用createView()方法,实现视图的创建。创建视图时,分两种情况,一种时启用缓存的,一种时不启用缓存的,具体实现如下:
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {//当不启用缓存时,即cacheLimit < 0时,直接创建视图
return createView(viewName, locale);
}
else {//启用缓存
//获取缓存的key,格式: viewName + '_' + locale
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);
//当创建视图,结果为null时
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
//视图不为空时,过滤视图,并把符合要求的视图,放到viewAccessCache和viewCreationCache中
if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
在createView()方法中,又通过调用loadView()方法实现视图的创建和加载,而该方法是一个抽象方法,交由子类进行实现。
@Nullable
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
@Nullable
protected abstract View loadView(String viewName, Locale locale) throws Exception;
5.2、UrlBasedViewResolver类
UrlBasedViewResolver类主要是一个实现了基于URL查找模板文件的视图解析器基类,设置了统一的查找模板的规则。在该类中,主要实现了createView()、loadView()、getCacheKey()方法,同时定义了一个buildView()方法。其中getCacheKey()方法是直接返回了viewName,去掉了locale的,因此这类视图解析器不支持本地化配置。代码如下:
@Override
protected Object getCacheKey(String viewName, Locale locale) {
return viewName;
}
createView()方法
createView()方法重新了父类中的方法,增加了统一的Redirect类型和FORWARD类型的处理,然后再交由父类的createView()方法处理。在父类中,又调用了loadView()方法,这里又调用了子类的loadView()方法。
@Override
protected View createView(String viewName, Locale locale) throws Exception {
//
if (!canHandle(viewName, locale)) {
return null;
}
// 检查是否以“redirect:”开头,作为Redirect类型处理
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl,
isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
}
// 检查是否以“forward:”开头,作为FORWARD类型处理
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
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.
return super.createView(viewName, locale);
}
loadView()方法
通过调用父类的createView()方法,最终又调用了子类的loadView()方法,这个时候,就调用了重新定义的一个buildView()方法,来构建视图。
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
buildView()方法
真正创建视图的方法,通过BeanUtils工具类,根据viewClass参数实例化视图,并为视图设置相关参数。具体实现如下:
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//获取视图的class
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");
//获取视图实例
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
//设置视图URL,包括前缀、后缀
view.setUrl(getPrefix() + viewName + getSuffix());
//设置参数
view.setAttributesMap(getAttributesMap());
//设置媒体类型
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
//设置ContextAttribute参数
String requestContextAttribute = getRequestContextAttribute();
if (requestContextAttribute != null) {
view.setRequestContextAttribute(requestContextAttribute);
}
//设置PathVariables参数
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;
}
5.3、InternalResourceViewResolver类
经过前面两个类的实现,到InternalResourceViewResolver类时,只需要在创建出来的视图的基础上设置了一些属性,并限制viewClass为InternalResourceView类型。其中,定义alwaysInclude属性用于标示是否在可以使用forward的情况下也强制使用include,默认为false。具体实现如下:
public class InternalResourceViewResolver extends UrlBasedViewResolver {
private static final boolean jstlPresent = ClassUtils.isPresent(
"javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
//用于标示是否在可以使用forward的情况下也强制使用include
@Nullable
private Boolean alwaysInclude;
//设置ViewClass,默认为InternalResourceView或者JstlView
public InternalResourceViewResolver() {
Class<?> viewClass = requiredViewClass();
if (InternalResourceView.class == viewClass && jstlPresent) {
viewClass = JstlView.class;
}
setViewClass(viewClass);
}
//设置前缀和后缀
public InternalResourceViewResolver(String prefix, String suffix) {
this();
setPrefix(prefix);
setSuffix(suffix);
}
//解析视图类型为InternalResourceView
@Override
protected Class<?> requiredViewClass() {
return InternalResourceView.class;
}
public void setAlwaysInclude(boolean alwaysInclude) {
this.alwaysInclude = alwaysInclude;
}
//重写父类,设置alwaysInclude和preventDispatchLoop属性
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
InternalResourceView view = (InternalResourceView) super.buildView(viewName);
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
view.setPreventDispatchLoop(true);
return view;
}
}
6、总结
在这一节中,我们主要分析了视图解析器的相关内容。和视图解析器息息相关的就是视图,我们下一节将详细分析视图的相关内容,然后再回头来看视图解析器,这样就会y有一个更全面的理解。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68792.html