ViewResolver的主要作用是根据视图名和Locale解析出视图,解析过程主要做了两件事,解析出使用的模板和视图的类型。ViewResolver的继承结构图如下所示:
SpringMVC中的ViewResolver整体可以分为四大类:AbstractCachingViewResolver、BeanNameViewResovler、ContentNegotiatingViewResolver和ViewResolverComposite。其中后三类每一类都只有一个实现类,而AbstractCachingViewResolver却一家独大,因为这个类下面可以缓存解析过的视图的基类,而逻辑视图和视图的关系一般是不变的,所以不需要每一都重新解析,最好解析一次就缓存起来,
1.BeanNameViewResolver是使用逻辑视图作为beanName从SpringMVC容器中查找。
2. ViewResolverComposite是一个封装着多个ViewResolver的容器,解析视图时遍历封装着的ViewResolver具体解析,不过ViewResolverComposite除了遍历成员解析视图外还给成员进行了必要的初始化,其中包括对实现了ApplicationContextAware接口的ViewResolver设置ApplicationContext、是给实现了ServletContextAware接口的ViewResolver设置ServletContext以及对实现了InitializingBean接口的ViewResolver调用afterPropertiesSet方法。
源码:
public class ViewResolverComposite implements ViewResolver, Ordered, InitializingBean, ApplicationContextAware, ServletContextAware {
private final List viewResolvers = new ArrayList();
private int order = 2147483647;
public ViewResolverComposite() {
}
public void setViewResolvers(List viewResolvers) {
this.viewResolvers.clear();
if (!CollectionUtils.isEmpty(viewResolvers)) {
this.viewResolvers.addAll(viewResolvers);
}
}
public List getViewResolvers() {
return Collections.unmodifiableList(this.viewResolvers);
}
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Iterator var2 = this.viewResolvers.iterator();
while(var2.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var2.next();
if (viewResolver instanceof ApplicationContextAware) {
((ApplicationContextAware)viewResolver).setApplicationContext(applicationContext);
}
}
}
public void setServletContext(ServletContext servletContext) {
Iterator var2 = this.viewResolvers.iterator();
while(var2.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var2.next();
if (viewResolver instanceof ServletContextAware) {
((ServletContextAware)viewResolver).setServletContext(servletContext);
}
}
}
public void afterPropertiesSet() throws Exception {
Iterator var1 = this.viewResolvers.iterator();
while(var1.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var1.next();
if (viewResolver instanceof InitializingBean) {
((InitializingBean)viewResolver).afterPropertiesSet();
}
}
}
//主要是这个方法用来解析View的
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
Iterator var3 = this.viewResolvers.iterator();
View view;
do {
if (!var3.hasNext()) {
return null;
}
ViewResolver viewResolver = (ViewResolver)var3.next();
view = viewResolver.resolveViewName(viewName, locale);
} while(view == null);
return view;
}
}
这个解析器的作用就是在别的解析器解析的结果上增加了对MediaType和后缀的支持,MediaType即媒体类型,有的地方页脚Content-Type,比如常用的text/html、text/javascript以及表示上传表单的multipart/form-data等。对视图的解析不是它本身完成的而是通过封装的ViewResolver来进行的。
整个过程:首先遍历所封装的ViewResolver具体解析视图,可能会解析出多个视图,然后再使用request获取MediaType,也可能有多个结果,然后这两个结果进行匹配找出最优视图。
属性中的ViewResolvers有两种初始化方式:一种是手动设置,另外一种是如果没有设置则自动获取spring容器中除了他自己外的所有ViewResolver并设置到ViewResolver中,如果是手动设置的,而且不在spring容器中,会对它进行初始化,
代码如下:
protected void initServletContext(ServletContext servletContext) {
//获取容器中所有的ViewResolver类型的bean,是整个spring容器,而不仅仅是springMVC
Collection matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
ViewResolver viewResolver;
//如果没有手动注册则将容器中找到的ViewResolver设置给ViewResolvers
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList(matchingBeans.size());
Iterator var3 = matchingBeans.iterator();
while(var3.hasNext()) {
viewResolver = (ViewResolver)var3.next();
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
} else {
//如果是手动注册,但是容器中不存在,则进行初始化
for(int i = 0; i < this.viewResolvers.size(); ++i) {
viewResolver = (ViewResolver)this.viewResolvers.get(i);
if (!matchingBeans.contains(viewResolver)) {
String name = viewResolver.getClass().getName() + i;
this.obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolver, name);
}
}
}
if (this.viewResolvers.isEmpty()) {
this.logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the 'viewResolvers' property on the ContentNegotiatingViewResolver");
}
//按照Order属性进行排序
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
解析视图的方法源码如下:
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
//使用RequestContextHolder获取RequestAttribute,进而获取request
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//通过request获取MediaType,用作需要满足的条件
List requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
//获取所有的候选视图,内部通过遍历封装的viewResolver来解析
List candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
//从多个候选视图中选出最好的一个
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
if (this.useNotAcceptableStatusCode) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code");
}
return NOT_ACCEPTABLE_VIEW;
} else {
this.logger.debug("No acceptable view found; returning null");
return null;
}
}
其中从多个候选图获取最好的视图的方法源码:
private View getBestView(List candidateViews, List requestedMediaTypes, RequestAttributes attrs) {
Iterator var4 = candidateViews.iterator();
//判断候选视图中有没有redirect视图,如果有直接返回
while(var4.hasNext()) {
View candidateView = (View)var4.next();
if (candidateView instanceof SmartView) {
SmartView smartView = (SmartView)candidateView;
if (smartView.isRedirectView()) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Returning redirect view [" + candidateView + "]");
}
return candidateView;
}
}
}
var4 = requestedMediaTypes.iterator();
while(var4.hasNext()) {
MediaType mediaType = (MediaType)var4.next();
Iterator var10 = candidateViews.iterator();
while(var10.hasNext()) {
View candidateView = (View)var10.next();
if (StringUtils.hasText(candidateView.getContentType())) {
//根据候选视图获取对应的MediaType
MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType());
//判断当前MediaType是否支持从候选视图获取对应的MediaType,如text/*可以支持test/html,text/css,text/xml等所有的text类型
if (mediaType.isCompatibleWith(candidateContentType)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Returning [" + candidateView + "] based on requested media type '" + mediaType + "'");
}
attrs.setAttribute(View.SELECTED_CONTENT_TYPE, mediaType, 0);
return candidateView;
}
}
}
}
return null;
}
这个解析器提供统一的缓存功能干呢个,当视图解析过一次就被缓存起来,缓存被删除前视图解析都会从缓存中获取。
它的直接继承类有三个:ResourceBundleViewResolver、XmlViewResolver和UrlBasedViewResolver。
这个抽象类中有一个cacheLimit参数,他是用来设置最大缓存书的,当设置为0时不启用缓存,isCache就是判断是否大于0,如果设置为一个大于0的数则表示最多可以缓存视图的数量,如果往里面添加视图超过了这个数那么最前面缓存的值将删除,其中LinkedHashMap中removeEldestEntry方法可以在返回true时,超过给定大小就会删除最前面的值,具体百度。
解析视图源码:
public View resolveViewName(String viewName, Locale locale) throws Exception {
//是否有缓存
if (!this.isCache()) {
return this.createView(viewName, locale);//创建视图
} else {
Object cacheKey = this.getCacheKey(viewName, locale);
//这里是通过concurrentHashMap的容器中获取的缓存
View view = (View)this.viewAccessCache.get(cacheKey);
if (view == null) {
Map var5 = this.viewCreationCache;
synchronized(this.viewCreationCache) {
view = (View)this.viewCreationCache.get(cacheKey);
if (view == null) {
//创建视图
view = this.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 (this.logger.isTraceEnabled()) {
this.logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return view != UNRESOLVED_VIEW ? view : null;
}
}
@Nullable
protected View createView(String viewName, Locale locale) throws Exception {
return this.loadView(viewName, locale);
}
//这是一个模板方法由子类实现
@Nullable
protected abstract View loadView(String var1, Locale var2) throws Exception;
这个重写了父类的getCacheKey、createView和loadView三个方法。
getCacheKey方法直接返回viewName,和原来父类的返回viewName+“_”+locale相比,子类覆盖而没有使用locale,说明这个UrlBasedViewResolver并没有使用Locale,只是用viewName。
源码:
protected Object getCacheKey(String viewName, Locale locale) {
return viewName;
}
createView源码:
protected View createView(String viewName, Locale locale) throws Exception {
//检查是否支持此逻辑视图,可以配置支持的模板
if (!this.canHandle(viewName, locale)) {
return null;
} else {
String forwardUrl;
//检查是不是redirect视图
if (viewName.startsWith("redirect:")) {
forwardUrl = viewName.substring("redirect:".length());
RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());
String[] hosts = this.getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return this.applyLifecycleMethods(viewName, view);
//检查是不是forward视图
} else if (viewName.startsWith("forward:")) {
forwardUrl = viewName.substring("forward:".length());
return new InternalResourceView(forwardUrl);
} else {
//如果都不是则调用父类的createView,也就会调用loadView()方法
return super.createView(viewName, locale);
}
}
}
loadView源码:
protected View loadView(String viewName, Locale locale) throws Exception {
//创建view
AbstractUrlBasedView view = this.buildView(viewName);
初始化view
View result = this.applyLifecycleMethods(viewName, view);
//检查模板是否存在,存在则返回,否则返回null
return view.checkResource(locale) ? result : null;
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
Class> viewClass = this.getViewClass();
Assert.state(viewClass != null, "No view class");
AbstractUrlBasedView view = (AbstractUrlBasedView)BeanUtils.instantiateClass(viewClass);
//这里给viewName加上前缀和后缀,可以通过配置设置
view.setUrl(this.getPrefix() + viewName + this.getSuffix());
String contentType = this.getContentType();
if (contentType != null) {
//contentType不为空设置给view
view.setContentType(contentType);
}
view.setRequestContextAttribute(this.getRequestContextAttribute());
view.setAttributesMap(this.getAttributesMap());
Boolean exposePathVariables = this.getExposePathVariables();
if (exposePathVariables != null) {
//这个表示让view使用PathVariables,可以在ViewResolver中设置,PathVariable就是处理器中@PathVariables注释的参数
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = this.getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
//这个不为空的时候设置给view,表示让view使用容器中注册的bean,此参数可以在ViewResolver中配置
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = this.getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
//这个不为空的时候设置给view,表示让view使用容器中注册的bean,此参数可以在ViewResolver中配置
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
上面方法Class> viewClass = this.getViewClass();可以在子类通过setViewClass()方法从新设置viewClass类型,另外还有一个requiredViewClass方法,它用于在设置视图时判断所设置的类型是否支持。UrlBasedViewResolver默认返回的AbstractUrlBasedView类型。源码如下:
public void setViewClass(@Nullable Class> viewClass) {
if (viewClass != null && !this.requiredViewClass().isAssignableFrom(viewClass)) {
throw new IllegalArgumentException("Given view class [" + viewClass.getName() + "] is not of type [" + this.requiredViewClass().getName() + "]");
} else {
this.viewClass = viewClass;
}
}
@Nullable
protected Class> getViewClass() {
return this.viewClass;
}
protected Class> requiredViewClass() {
return AbstractUrlBasedView.class;
}
补充:
UrlBasedViewResolver的子类主要做三件事:
这两个都是继承UrlBasedViewResolver,前者用来解析jsp,后者用来解析FreeMarker视图。
public class InternalResourceViewResolver extends UrlBasedViewResolver {
private static final boolean jstlPresent = ClassUtils.isPresent("javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
@Nullable
private Boolean alwaysInclude;
//这里如果类型是jstlPresent,viewClass会使用JstlView.class
public InternalResourceViewResolver() {
Class> viewClass = this.requiredViewClass();
if (InternalResourceView.class == viewClass && jstlPresent) {
viewClass = JstlView.class;
}
this.setViewClass(viewClass);
}
public InternalResourceViewResolver(String prefix, String suffix) {
this();
this.setPrefix(prefix);
this.setSuffix(suffix);
}
//返回的类型是InternalResourceView
protected Class> requiredViewClass() {
return InternalResourceView.class;
}
public void setAlwaysInclude(boolean alwaysInclude) {
this.alwaysInclude = alwaysInclude;
}
//新添加了alwaysInclude属性
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
InternalResourceView view = (InternalResourceView)super.buildView(viewName);
//这个表示是否在使用forward的情况下也强制使用include,默认是false,可以在注册解析器时配置
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
//用于阻止循环调用,也就是请求处理完成后又转发回了原来使用的处理器的情况
view.setPreventDispatchLoop(true);
return view;
}
}
这个类继承UrlBasedViewResolver的子类AbstractTemplateViewResolver,AbstractTemplateViewResolver是所用模板ViewResolver的父类,它里面主要对创建的View设置一些属性,并将requiredViewClass的返回值设置为AbstractTemplateView类型。
1)AbstractTemplateViewResolver源码:
public class AbstractTemplateViewResolver extends UrlBasedViewResolver {
private boolean exposeRequestAttributes = false;
private boolean allowRequestOverride = false;
private boolean exposeSessionAttributes = false;
private boolean allowSessionOverride = false;
private boolean exposeSpringMacroHelpers = true;
//...setter和getter方法
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractTemplateView view = (AbstractTemplateView)super.buildView(viewName);
//是否将requestAttributes暴露给view,默认为false
view.setExposeRequestAttributes(this.exposeRequestAttributes);
//当requestAttributes中存在Model中同名的参数,是否允许将Model中的值覆盖,默认false
view.setAllowRequestOverride(this.allowRequestOverride);
//是否将SessionAttribute暴露给view使用,默认是false
view.setExposeSessionAttributes(this.exposeSessionAttributes);
//当SessionAttributes中存在Model中同名的参数,是否使用requestAttributes的值将Model中的覆盖,默认false
view.setAllowSessionOverride(this.allowSessionOverride);
view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
return view;
}
}
2)FreeMarkerViewResolver 源码:
这个只需要覆盖requiredViewClass方法就返回freeMakerView类型,如下:
public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {
public FreeMarkerViewResolver() {
this.setViewClass(this.requiredViewClass());
}
public FreeMarkerViewResolver(String prefix, String suffix) {
this();
this.setPrefix(prefix);
this.setSuffix(suffix);
}
protected Class> requiredViewClass() {
return FreeMarkerView.class;
}
}
大部分实现类都是继承AbstractCachingViewResolver,他提供了对解析结果进行缓存的统一解决方法,他的子类中ResourceBundlerViewResolver和XmlViewResolver分别通过properties和xml配置文件进行解析。UrlBasedViewResolver将viewName添加前后缀用作url,他的子类只需要提供视图类型就可以了。
除了AbstractCachingViewResolver,还有三个类,BeanNameViewResolver、ContentNegotiatingViewResolver和ViewResolverComposite。第一个是通过在Spring容器里使用viewName查找bean来作为View;第二个是使用内部封装的ViewResolver解析后再根据MediaType或者后缀找出最优的视图;第三个直接遍历内部封装的ViewResolver进行解析。这三个都是不需要缓存的。
解析视图的核心工作是查找模板文件和视图类型,而查找的主要参数只有viewName,有三种解析思路: