在线咨询

深入剖析Spring Web源码(十八) - 视图解析和视图显示 - 更多的视图解析器


4.3.2 更多的视图解析器

 

上一节中,我们介绍了基于URL的视图解析器和视图的实现。事实上,还存在这一些视图解析器,他们不仅支持某一种类型的视图实现,而是能在多种视图实现中互相转换和选择。这一节中我们将分析四种这样类型的视图解析器的实现。

 

4.3.2.1 Bean名视图解析器

 

Bean名视图解析器通过把逻辑视图名作为Web应用程序环境中的Bean名来解析视图。如下代码所示,

 

public   class  BeanNameViewResolver  extends  WebApplicationObjectSupport  implements  ViewResolver, Ordered {

 

     private   int  order = Integer. MAX_VALUE ;   // default: same as non-Ordered

 

 

     public   void  setOrder( int  order) {

         this .order = order;

    }

 

     public   int  getOrder() {

         return  order;

    }

 

 

     public  View resolveViewName(String viewName, Locale locale)  throws  BeansException {

        取得Web应用程序环境

        ApplicationContext context = getApplicationContext();

       

        如果Web应用程序环境中不包含一个Bean,它的名字是逻辑视图名,则返回空的视图

         if  (!context.containsBean(viewName)) {

             // Allow for ViewResolver chaining.

             return   null ;

        }

       

        如果Web应用程序环境中包含这样的一个Bean,则返回这个Bean作为视图

         return  (View) context.getBean(viewName, View. class );

    }

 

}

 

4.3.2.2 内容选择视图解析器

内容选择视图解析器根据HTTP请求所指定的媒体类型来选择一个合适的视图解析器来解析视图,但是它自己并不解析视图。一个HTTP请求可以通过下列的方式之一来制定媒体类型,

 

1.       根据URL路径的扩展名。

2.       根据制定的参数值。

3.       根据HTTP头的接受内容类型。

 

首先在初始化的时候它加载了所有的其他的视图解析器。如下代码注释,

 

@Override

protected   void  initServletContext(ServletContext servletContext) {

     if  ( this .viewResolvers ==  null ) {

         //  Web 应用程序环境中找到所有的视图解析器的实现

        Map<String, ViewResolver> matchingBeans =

                BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver. class );

         this .viewResolvers =  new  ArrayList<ViewResolver>(matchingBeans.size());

         for  (ViewResolver viewResolver : matchingBeans.values()) {

             //  保存所有的视图解析器,并且排除自己

             if  ( this  != viewResolver) {

                 this .viewResolvers.add(viewResolver);

            }

        }

    }

   

     //  如果不能找到视图解析器,则打印警告日志

     if  ( this .viewResolvers.isEmpty()) {

        logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " +

                "'viewResolvers' property on the ContentNegotiatingViewResolver");

    }

   

     //  排序视图解析器, ContentNegotiatingViewResolver 的优先级是最高的

    OrderComparator.sort( this .viewResolvers);

}

 

当处理一个HTTP请求的时候,它首先通过上述的三个规则解析请求所指定的媒体类型,如下代码所示,

 

protected  List<MediaType> getMediaTypes(HttpServletRequest request) {

     //  如果扩展名解析优先,从 URL 的扩展名中解析媒体类型

     if  ( this .favorPathExtension) {

        String requestUri = urlPathHelper.getRequestUri(request);

        String filename = WebUtils.extractFullFilenameFromUrlPath(requestUri);

        MediaType mediaType = getMediaTypeFromFilename(filename);

         if  (mediaType !=  null ) {

             if  (logger.isDebugEnabled()) {

                logger.debug("Requested media type is '" + mediaType + "' (based on filename '" + filename + "')");

            }

            List<MediaType> mediaTypes =  new  ArrayList<MediaType>();

            mediaTypes.add(mediaType);

             return  mediaTypes;

        }

    }

   

     //  如果参数解析优先,从参数中解析媒体类型

     if  ( this .favorParameter) {

         if  (request.getParameter( this .parameterName) !=  null ) {

            String parameterValue = request.getParameter( this .parameterName);

            MediaType mediaType = getMediaTypeFromParameter(parameterValue);

             if  (mediaType !=  null ) {

                 if  (logger.isDebugEnabled()) {

                    logger.debug("Requested media type is '" + mediaType + "' (based on parameter '" +

                             this .parameterName + "'='" + parameterValue + "')");

                }

                List<MediaType> mediaTypes =  new  ArrayList<MediaType>();

                mediaTypes.add(mediaType);

                 return  mediaTypes;

            }

        }

    }

   

     //  如果不忽略 HTTP 头中支持的接受头信息,则从 HTTP 头中解析媒体类型

     if  (! this .ignoreAcceptHeader) {

        String acceptHeader = request.getHeader(ACCEPT_HEADER);

         if  (StringUtils.hasText(acceptHeader)) {

            List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);

             if  (logger.isDebugEnabled()) {

                logger.debug("Requested media types are " + mediaTypes + " (based on Accept header)");

            }

             return  mediaTypes;

        }

    }

   

     //  如果解析不成功,则使用缺省内容类型

     if  ( this .defaultContentType !=  null ) {

         return  Collections.singletonList( this .defaultContentType);

    }

     else  {

         return  Collections.emptyList();

    }

}

 

在得知一个请求所能接受的媒体类型后,它将要选择一个最佳的能处理当前媒体类型的视图解析器来解析具体的视图,如下代码注释,

 

public  View resolveViewName(String viewName, Locale locale)  throws  Exception {

     //  取得 Servlet 请求属性

    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();

    Assert.isInstanceOf(ServletRequestAttributes. class , attrs);

    ServletRequestAttributes servletAttrs = (ServletRequestAttributes) attrs;

 

     //  Servlet 请求属性中得到 HTTP 请求对象

    List<MediaType> requestedMediaTypes = getMediaTypes(servletAttrs.getRequest());

     //  如果支持的媒体类型多余一个,则进行排序

     if  (requestedMediaTypes.size() > 1) {

         // avoid sorting attempt for empty list and singleton list

        Collections.sort(requestedMediaTypes);

    }

 

     //  如果某个视图解析器能够解析当前请求,解析的视图则成为候选视图

    SortedMap<MediaType, View> views =  new  TreeMap<MediaType, View>();

    List<View> candidateViews =  new  ArrayList<View>();

     for  (ViewResolver viewResolver :  this .viewResolvers) {

        View view = viewResolver.resolveViewName(viewName, locale);

         if  (view !=  null ) {

            candidateViews.add(view);

        }

    }

   

     //  缺省视图也是候选视图

     if  (!CollectionUtils.isEmpty( this .defaultViews)) {

        candidateViews.addAll( this .defaultViews);

    }

 

     //  遍历所有候选视图

     for  (View candidateView : candidateViews) {

         //  取得候选试图支持的内容类型

        String contentType = candidateView.getContentType();

         if  (StringUtils.hasText(contentType)) {

             //  转换成为媒体类型对象

            MediaType viewMediaType = MediaType.parseMediaType(contentType);

             //  遍历 HTTP 请求支持的媒体类型

             for  (MediaType requestedMediaType : requestedMediaTypes) {

                 //  如果 HTTP 请求支持的媒体类型包含候选视图的媒体类型,则使用这个视图

                 if  (requestedMediaType.includes(viewMediaType)) {

                     if  (!views.containsKey(requestedMediaType)) {

                        views.put(requestedMediaType, candidateView);

                         break ;

                    }

                }

            }

        }

    }

 

     //  如果解析到一个或者多个视图 , 则使用第一个

     if  (!views.isEmpty()) {

        MediaType mediaType = views.firstKey();

        View view = views.get(mediaType);

         if  (logger.isDebugEnabled()) {

            logger.debug("Returning [" + view + "] based on requested media type '" + mediaType +"'");

        }

         return  view;

    }

     else  {

         return   null ;

    }

}

 

4.3.2.3 资源绑定视图解析器

资源绑定视图解析器从资源绑定中加载Bean定义,然后通过视图逻辑名来解析一个定义的Bean作为视图名,如下代码注释,

 

@Override

protected  View loadView(String viewName, Locale locale)  throws  Exception {

    从资源绑定中加载Bean工厂

    BeanFactory factory = initFactory(locale);

     try  {

        在Bean工厂中解析Bean

         return  factory.getBean(viewName, View. class );

    }

     catch  (NoSuchBeanDefinitionException ex) {

         // to allow for ViewResolver chaining

         return   null ;

    }

}

 

protected   synchronized  BeanFactory initFactory(Locale locale)  throws  BeansException {

     // Try to find cached factory for Locale:

     // Have we already encountered that Locale before?

     if  (isCache()) {

        BeanFactory cachedFactory =  this .localeCache.get(locale);

         if  (cachedFactory !=  null ) {

             return  cachedFactory;

        }

    }

 

     // Build list of ResourceBundle references for Locale.

    List<ResourceBundle> bundles =  new  LinkedList<ResourceBundle>();

     for  (String basename :  this .basenames) {

        ResourceBundle bundle = getBundle(basename, locale);

        bundles.add(bundle);

    }

 

     // Try to find cached factory for ResourceBundle list:

     // even if Locale was different, same bundles might have been found.

     if  (isCache()) {

        BeanFactory cachedFactory =  this .bundleCache.get(bundles);

         if  (cachedFactory !=  null ) {

             this .localeCache.put(locale, cachedFactory);

             return  cachedFactory;

        }

    }

 

     // Create child ApplicationContext for views.

    GenericWebApplicationContext factory =  new  GenericWebApplicationContext();

    factory.setParent(getApplicationContext());

    factory.setServletContext(getServletContext());

 

     // Load bean definitions from resource bundle.

    PropertiesBeanDefinitionReader reader =  new  PropertiesBeanDefinitionReader(factory);

    reader.setDefaultParentBean( this .defaultParentView);

     for  (ResourceBundle bundle : bundles) {

        reader.registerBeanDefinitions(bundle);

    }

 

    factory.refresh();

 

     // Cache factory for both Locale and ResourceBundle list.

     if  (isCache()) {

         this .localeCache.put(locale, factory);

         this .bundleCache.put(bundles, factory);

    }

 

     return  factory;

}

 

4.3.2.4 XML视图解析器

XML视图解析器从一个XML资源文件中加载Bean定义,然后通过视图逻辑名来解析一个定义的Bean作为视图名,如下代码注释,

 

protected   synchronized  BeanFactory initFactory()  throws  BeansException {

    检查是否已经缓存

     if  ( this .cachedFactory !=  null ) {

         return   this .cachedFactory;

    }

 

    从配置的位置或者缺省的位置加载XML资源

    Resource actualLocation =  this .location;

     if  (actualLocation ==  null ) {

        actualLocation = getApplicationContext().getResource(DEFAULT_LOCATION);

    }

 

     // Create child ApplicationContext for views.

    GenericWebApplicationContext factory =  new  GenericWebApplicationContext();

    factory.setParent(getApplicationContext());

    factory.setServletContext(getServletContext());

 

     // Load XML resource with context-aware entity resolver.

    XmlBeanDefinitionReader reader =  new  XmlBeanDefinitionReader(factory);

    reader.setEntityResolver( new  ResourceEntityResolver(getApplicationContext()));

    reader.loadBeanDefinitions(actualLocation);

 

    factory.refresh();

 

     if  (isCache()) {

         this .cachedFactory = factory;

    }

     return  factory;

}

转载请注明出处【 http://sishuok.com/article-detail.html?t=article-123&n=5384 】