首先我们来看下视图解析器的结构
1、ViewResolver接口
public interface ViewResolver { @Nullable View resolveViewName(String viewName, Locale locale) throws Exception; }
可以看到这个resolveViewName的入参是viewName即环境编码内容Locale,然后返回的是View视图。所以这个方法就是根据viewName去创建一个View。
下面我们来看下其的实现,其主要有两个分支的实现:1、直接实现类BeanNameViewResolver,2、基础底层抽象实现类AbstractCachingViewResolver,下面我们先来看下BeanNameViewResolver类。
2、BeanNameViewResolver分支
1、结构
这里关于WebApplicationObjectSupport就不具体分析了。通过其的Aware接口可以知道其获取了ApplicationContext、ServletContext,所以其可以通过这些对象获取一些想要的内容。这里我们需要注意的是其处理实现ViewResolver接口,其还有实现Ordered接口用于排序(不过其实Spring的接口一般有继承Ordered接口,但我们一般没有注意,不过在关于视图解析器的实时还是很有一些影响,这个到后面再具体分析)。
2、方法
1、resolveViewName
我们直接看其对于resolveViewName接口的实现
public View resolveViewName(String viewName, Locale locale) throws BeansException { ApplicationContext context = obtainApplicationContext(); if (!context.containsBean(viewName)) { return null; } if (!context.isTypeMatch(viewName, View.class)) { return null; } return context.getBean(viewName, View.class); }
可以看到这个方法是获取ApplicationContext、再通过ApplicationContext判断当前BeanFactory容器中有没有这个名称的Bean。如果没有就返回null,如果有的话再判断这个Bean是不是View.class类型的,这个也通过的话,就正式从Bean容器中获取这个Bean对象(View)。这个实现还是比较简单。
3、AbstractCachingViewResolver分支
下面我们来看下关于ViewResolver视图解析器中最大的一个分支即AbstractCachingViewResolver
1、AbstractCachingViewResolver
1、结构
可以看到其与前面BeanNameViewResolver类似,都有继承WebApplicationObjectSupport,然后实现ViewResolver(这里没有实现Ordered接口,一般由其子类去实现)。
2、方法
关于AbstractCachingViewResolver的方法,其关于Cache的内容我们不过多解析(因为要放到Cache中,首先需要创建,所以我们关注其创建过程,并且这里的缓存其实并不像创建Bean的时候那样重要)
1、resolveViewName
public View resolveViewName(String viewName, Locale locale) throws Exception { if (!isCache()) { return createView(viewName, locale); } else { 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); if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } if (view != null) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); } } } } return (view != UNRESOLVED_VIEW ? view : null); } }
public boolean isCache() { return (this.cacheLimit > 0); }
这里最前面的isCache就是判断存放的cache为不为空,如果为空的话就可以直接在这里调用createView,而不用到下面去使用createView,下面的关键也是createView方法。
2、createView
protected View createView(String viewName, Locale locale) throws Exception { return loadView(viewName, locale); }
3、loadView
protected abstract View loadView(String viewName, Locale locale) throws Exception;
可以看到最终会调用到loadView去创建View,创建好了就会将子类创建的View放到AbstractCachingViewResolver的缓存中来。
然后关于这个loadView
获取的方法,我们可以看到有三个分支实现:
我们主要看下UrlBasedViewResolver、ResourceBundleViewResolver类,UrlBasedViewResolver还有其它的子类
2、UrlBasedViewResolver
1、结构
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
可以看到其是继承AbstractCachingViewResolver类,同时实现了在AbstractCachingViewResolver没有实现的Ordered接口。
2、主要方法
1、setViewClass
public void setViewClass(@Nullable Class<?> viewClass) { if (viewClass != null && !requiredViewClass().isAssignableFrom(viewClass)) { throw new IllegalArgumentException("Given view class [" + viewClass.getName() + "] is not of type [" + requiredViewClass().getName() + "]"); } this.viewClass = viewClass; }
可以看到这里有一个方法(有对应的成员变量viewClass )去设置View的class描叙。
2、createView
protected View createView(String viewName, Locale locale) throws Exception { if (!canHandle(viewName, locale)) { return null; } if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); ........ return applyLifecycleMethods(viewName, view); } if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView(forwardUrl); } return super.createView(viewName, locale); }
可以看到其重写了createView方法,并在最后调用了父类的createView(也就是调用loadView方法,同时loadView在父类是抽象方法,所以又会调用其实现的抽象方法loadView)。
这个方法实现是canHandle方法,判断能不能进行处理,如果返回false表示不能就直接返回null。
接下来就共有三个分支处理了:1、如果viewName的前缀是以REDIRECT_URL_PREFIX(REDIRECT_URL_PREFIX = "redirect:")开头,其创建的View就是RedirectView。2、如果viewName的前缀是以FORWARD_URL_PREFIX (FORWARD_URL_PREFIX = "forward:"开头,其创建的View就是InternalResourceView。3、如果不是这两种开头,就调用分类的createView方法去创建(也就是该子类实现的loadView方法)。
下面我们来看上面createView涉及的几个方法。
3、canHandle
protected boolean canHandle(String viewName, Locale locale) { String[] viewNames = getViewNames(); return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName)); }
该方法首先是获取viewNames(SpringMVC一般没有主动设置这个值),如果为空就返回true,所以承接上面createView方法就是接着调用下面方法去创建View,不会直接返回null。如果不为空再调用方法去简单匹配,看能不能匹配。
4、applyLifecycleMethods
protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) { ApplicationContext context = getApplicationContext(); if (context != null) { Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName); if (initialized instanceof View) { return (View) initialized; } } return view; }
这个applyLifecycleMethods方法,就是对创建的view对象进行初始化,调用BeanFactory的initializeBean方法,这个方法就是去调用一些接口来处理这个bean(view对象),在前面梳理Bean创建的时候有分析,我们现在来简单看下。
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { ...... invokeAwareMethods(beanName, bean); Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd); } .......... if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
可以看到这个方法就是先执行一些Aware接口,然后执行BeanPostProcessor接口的postProcessBeforeInitialization方法,再执行InitializingBean接口,最后执行BeanPostProcessor的postProcessAfterInitialization方法。
5、loadView
@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); }
可以看到这个loadView方法的实现,实现是通过buildView方法去创建view,然后也是和上面一样调用applyLifecycleMethods方法。
6、buildView
protected AbstractUrlBasedView buildView(String viewName) throws Exception { AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass()); 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); } ............. return view; } public Map<String, Object> getAttributesMap() { return this.staticAttributes; } public void setContentType(@Nullable String contentType) { this.contentType = contentType; }
这里的创建过程也比较简单其实,就是通过ViewClass去直接创建,然后去设置View的url,通过前缀(Prefix)+viewName+后缀(Suffix),这里是不是比较熟悉。然后是设置一些AbstractUrlBasedView的属性值。
现在为了增加熟悉感,提升兴趣,我们提前下,看下我们平常配置的demo:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="jspViewResolver"> <property value="org.springframework.web.servlet.view.JstlView" name="viewClass"/> <property value="/WEB-INF/" name="prefix"/> <property value=".jsp" name="suffix"/> </bean>
平常我们一般是怎样去配置一个视图解析器(ViewResolver)的,这里是不是熟悉的三个内容。按照这个配置,viewClass为JstlView,所以就会创建一个JstlView视图,然后这个视图的url就是通过prefix、suffix去拼接的一个完整的获取地址。同时你一个可以通过xml文件去设置context-type,以及全局Model staticAttributes,在这里就会将这个全局Model设置到View中。
然后可以看到其的子类有:
这里的AbstractTemplateViewResolver与FreeMarkerViewResolver、GroovyMarkupViewResolver,这两个FreeMark、Groovy都是模板处理,类似于Thymeleaf模板。然后我们也关注下前面demo讲的InternalResourceViewResolver,通过一些实现了解处理流程,并不把所有实现都梳理一遍。
3、InternalResourceViewResolver
InternalResourceViewResolver其继承于UrlBasedViewResolver
public class InternalResourceViewResolver extends UrlBasedViewResolver { @Nullable private Boolean alwaysInclude; public InternalResourceViewResolver() { Class<?> viewClass = requiredViewClass(); if (InternalResourceView.class == viewClass && jstlPresent) { viewClass = JstlView.class; } setViewClass(viewClass); } .............. @Override protected Class<?> requiredViewClass() { return InternalResourceView.class; } ................ @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; } }
可以看到其重写了buildView方法,这个是调用其父类创建InternalResourceView,然后多的部分就是设置这个View的两个属性,关于alwaysInclude,这个没人为空,除非是主动有设置,这个其实就是forward、include的区别。
public class InternalResourceView extends AbstractUrlBasedView { ............. RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); ........... if (useInclude(request, response)) { response.setContentType(getContentType()); rd.include(request, response); } else { rd.forward(request, response); } } protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) { return (this.alwaysInclude || WebUtils.isIncludeRequest(request) || response.isCommitted()); }
4、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; @Override protected Class<?> requiredViewClass() { return AbstractTemplateView.class; } ....... @Override protected AbstractUrlBasedView buildView(String viewName) throws Exception { 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; } }
这个也是重写buildView方法,调用父类后再设置对应属性(默认为false)。该类一般是用于给两个模板ViewResolver处理的:
我们看下FreeMarkerViewResolver。
5、FreeMarkerViewResolver
public class FreeMarkerViewResolver extends AbstractTemplateViewResolver { public FreeMarkerViewResolver() { setViewClass(requiredViewClass()); } public FreeMarkerViewResolver(String prefix, String suffix) { this(); setPrefix(prefix); setSuffix(suffix); } @Override protected Class<?> requiredViewClass() { return FreeMarkerView.class; } }
这个类主要是requiredViewClass重写(没有进行逻辑代码删除,删除了注释)。通过上面几个ViewResolver,我们也有注意到,一般都有重写requiredViewClass方法,并返回本类的Class,然后在规则函数中调用 setViewClass(requiredViewClass());,这样就设置了父类UrlBasedViewResolver的viewClass,这样就能将本类创建为对象。同时在这些ViewResolver中我们也有注意到,不同的ViewResolver在不同的情况下就会创建对应不同的View(简单来说就是ViewResolver与View的创建是对应的)。