SpringMVC源码分析-ViewResolver

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<ViewResolver> viewResolvers = new ArrayList();
    private int order = 2147483647;

    public ViewResolverComposite() {
    }

    public void setViewResolvers(List<ViewResolver> viewResolvers) {
        this.viewResolvers.clear();
        if (!CollectionUtils.isEmpty(viewResolvers)) {
            this.viewResolvers.addAll(viewResolvers);
        }

    }

    public List<ViewResolver> 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;
    }
}

3.ContentNegotiatingViewResolver

     这个解析器的作用就是在别的解析器解析的结果上增加了对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<ViewResolver> 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<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
        if (requestedMediaTypes != null) {
            //获取所有的候选视图,内部通过遍历封装的viewResolver来解析
            List<View> 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<View> candidateViews, List<MediaType> 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;
    }

4.AbstractCachingViewResolver

      这个解析器提供统一的缓存功能干呢个,当视图解析过一次就被缓存起来,缓存被删除前视图解析都会从缓存中获取。

 它的直接继承类有三个:ResourceBundleViewResolver、XmlViewResolver和UrlBasedViewResolver。

  • ResourceBundleViewResolver是通过使用properties属性配置文件解析视图;
  • XmlViewResolver是通过xml配置文件解析视图
  • UrlBasedViewResolver是所有直接将逻辑视图作为url查找模板文件的ViewResolver的基类,这个类设置了统一的查找模板的规则,它的子类只需要确定渲染方式就可以确定视图类型,它的每一个子类都对应一种视图类型。

 4.1 AbstractCachingViewResolver 解析视图

       这个抽象类中有一个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;

     4.2 UrlBasedViewResolver

            这个重写了父类的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的子类主要做三件事:

  • 通过重写requiredViewClass方法修改了必须符合视图类型的值;
  • 使用setViewClass方法设置了使用的视图类型;
  • 给创建出来的视图设置一些属性
  •  

 4.3 InternalResourceViewResolver和FreeMarkerViewResolver

     这两个都是继承UrlBasedViewResolver,前者用来解析jsp,后者用来解析FreeMarker视图。

    4.3.1 InternalResourceViewResolver源码,相关注释也在其中:

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;
    }
}

      4.3.2 FreeMarkerViewResolver 

                    这个类继承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,有三种解析思路:

  • 使用viewName查找模板文件---对应URLBasedViewResolver;
  • 使用viewName查找视图类型--对应BeanNameViewResolver;
  • 使用viewName同时查找视图类型和模板文件--对应ResourceBundlerViewResolver和XmlViewResolver;

猜你喜欢

转载自blog.csdn.net/weixin_40792878/article/details/82025428
今日推荐