SpringMVC源码解读 --- 视图解析器(ViewResolver)的结构及源码分析

    首先我们来看下视图解析器的结构

  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的创建是对应的)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值