在线咨询

深入剖析Spring Web源码(十七) - 视图解析和视图显示 - 基于URL的视图解析器和视图


4.3 视图解析和视图显示

上一节,深入的剖析了作为总控制器的派遣器Servlet如何通过处理器映射查找处理器,并且通过处理器适配器进行适配调用处理器实现的业务逻辑服务,进而返回逻辑视图名和模型数据对象。

这一节,我们将分析作为总控制器的派遣器Servlet如何解析视图和显示视图。这是Spring Web MVC流程中最后一个关键步骤。

由于视图显示技术的多样性,存在很多视图解析器和视图的实现,这一节我们将分析典型的视图解析器和视图显示的实现。他们是基于URL的视图解析器,然后,对于其他更多的视图解析器和视图的实现进行剖析。

4.3.1 基于URL的视图解析器和视图

基于URL的视图解析器是视图解析器接口的简单实现,它不需要显示的映射定义,而是直接的把逻辑视图名解析成为资源URL。它通常使用在基于URL的简单的视图显示技术上,并且逻辑视图名是URL的一部分。如下视图解析器和视图的类图所示,

 

 

 

图表 4‑40

 

 

 

 

图表 4‑41

 

抽象缓存视图解析器是视图解析器的直接的抽象实现类。它实现了对视图的内存缓存。如下代码所示,

public   abstract   class  AbstractCachingViewResolver  extends  WebApplicationObjectSupport  implements ViewResolver {

     //  设置缓存是否开启

     private   boolean  cache =  true ;

 

     //  缓存视图的映射对象

     private   final  Map<Object, View> viewCache =  new  HashMap<Object, View>();

 

     public   void  setCache( boolean  cache) {

         this .cache = cache;

    }

 

     public   boolean  isCache() {

         return   this .cache;

    }

 

     //  解析视图方法的逻辑实现

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

         //  如果视图缓存关闭,则每次都创建视图

         if  (!isCache()) {

             return  createView(viewName, locale);

        }

         //  如果视图缓存打开

         else  {

             //  首先取得缓存中存储视图的关键字对象

            Object cacheKey = getCacheKey(viewName, locale);

             //  需要同步存取

             synchronized  ( this .viewCache) {

                 //  检查缓存中是否已经存在视图

                View view =  this .viewCache.get(cacheKey);

                 if  (view ==  null ) {

                     // Ask the subclass to create the View object.

                    view = createView(viewName, locale);

                     //  将创建的视图加入到缓存中

                     this .viewCache.put(cacheKey, view);

                     if  (logger.isTraceEnabled()) {

                        logger.trace("Cached view [" + cacheKey + "]");

                    }

                }

                 //  返回从缓存中获得的视图或者新创建的视图

                 return  view;

            }

        }

    }

 

     protected  Object getCacheKey(String viewName, Locale locale) {

         //  使用逻辑视图名和地域作为视图的关键字

         return  viewName + "_" + locale;

    }

 

     //  提供功能移除缓存的视图

     public   void  removeFromCache(String viewName, Locale locale) {

         if  (! this .cache) {

            logger.warn("View caching is SWITCHED OFF -- removal not necessary");          

        }

         else  {

            Object cacheKey = getCacheKey(viewName, locale);

            Object cachedView;

             synchronized  ( this .viewCache) {

                cachedView =  this .viewCache.remove(cacheKey);

            }

             if  (cachedView ==  null ) {

                 // Some debug output might be useful...

                 if  (logger.isDebugEnabled()) {

                    logger.debug("No cached instance for view '" + cacheKey + "' was found");

                }

            }

             else  {

                 if  (logger.isDebugEnabled()) {

                    logger.debug("Cache for view " + cacheKey + " has been cleared");

                }

            }

        }

    }

 

     //  提供功能清除所有缓存的视图

     public   void  clearCache() {

        logger.debug("Clearing entire view cache");

         synchronized  ( this .viewCache) {

             this .viewCache.clear();

        }

    }

 

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

         return  loadView(viewName, locale);

    }

 

     //  让子类根据具体实现创建不同的视图

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

 

}

视图解析器实现体系结构中下一个层次是基于URL的视图解析器,它除了根据重定向前缀和转发前缀构造一个重定向视图和转发视图以外,它还为每个通常的URL创建一个抽象的基于URL视图的子类对象并且返回。如下代码注释,

public   class  UrlBasedViewResolver  extends  AbstractCachingViewResolver  implements  Ordered {

     //  重定向前缀

     public   static   final  String  REDIRECT_URL_PREFIX  = "redirect:";

 

     //  转发前缀

     public   static   final  String  FORWARD_URL_PREFIX  = "forward:";

 

     //  视图类名,应该是抽象的基于 URL 视图的子类

     private  Class viewClass;

 

     // URL 前缀

     private  String prefix = "";

 

     // URL 后缀

     private  String suffix = "";

 

     //  能够处理的视图逻辑名字集合,如果为空,任何视图逻辑名都可以处理

     private  String[] viewNames =  null ;

 

     //  支持的内容类型

     private  String contentType;

 

     //  / 开头的 URL 是相对于 Web Server 根还是应用程序根

     private   boolean  redirectContextRelative =  true ;

 

     //  使用 HTTP 1.0 兼容还是使用 HTTP1.1 兼容,不同版本的 HTTP 协议重定向方法不同

     private   boolean  redirectHttp10Compatible =  true ;

 

     //  保存请求环境属性的关键字

     private  String requestContextAttribute;

 

     //  最大顺序

     private   int  order = Integer. MAX_VALUE ;

 

     //  静态的属性,应用到所有的解析的视图上

     private   final  Map<String, Object> staticAttributes =  new  HashMap<String, Object>();

 

     //  设置视图类,需要的视图必须是抽象的基于 URL 的视图

     public   void  setViewClass(Class viewClass) {

         if  (viewClass ==  null  || !requiredViewClass().isAssignableFrom(viewClass)) {

             throw   new  IllegalArgumentException(

                    "Given view class [" + (viewClass !=  null  ? viewClass.getName() :  null ) +

                    "] is not of type [" + requiredViewClass().getName() + "]");

        }

         this .viewClass = viewClass;

    }

 

     protected  Class requiredViewClass() {

         return  AbstractUrlBasedView. class ;

    }

 

    @Override

     protected   void  initApplicationContext() {

         super .initApplicationContext();

         //  初始化是检查必要的是视图类

         if  (getViewClass() ==  null ) {

             throw   new  IllegalArgumentException("Property 'viewClass' is required");

        }

    }

   

    @Override

     protected  Object getCacheKey(String viewName, Locale locale) {

         //  仅仅使用视图逻辑名作为缓存的关键字

         return  viewName;

    }

 

    @Override

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

         // If this resolver is not supposed to handle the given view,

         // return null to pass on to the next resolver in the chain.

         if  (!canHandle(viewName, locale)) {

             return   null ;

        }

         // Check for special "redirect:" prefix.

         if  (viewName.startsWith( REDIRECT_URL_PREFIX )) {

            String redirectUrl = viewName.substring( REDIRECT_URL_PREFIX .length());

             return   new  RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());

        }

         // Check for special "forward:" prefix.

         if  (viewName.startsWith( FORWARD_URL_PREFIX )) {

            String forwardUrl = viewName.substring( FORWARD_URL_PREFIX .length());

             return   new  InternalResourceView(forwardUrl);

        }

         // Else fall back to superclass implementation: calling loadView.

         return   super .createView(viewName, locale);

    }

 

     protected   boolean  canHandle(String viewName, Locale locale) {

         //  如果声明了可以处理的视图结合,则进行匹配,如果匹配成功,则能够处理,否则,不能处理当前逻辑视图

        String[] viewNames = getViewNames();

         return  (viewNames ==  null  || PatternMatchUtils.simpleMatch(viewNames, viewName));

    }

 

    @Override

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

         //  创建抽象的基于 URL 的视图

        AbstractUrlBasedView view = buildView(viewName);

         //  将创建的视图放入到 Web 应用程序环境中,并且进行初始化

        View result = (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);

         //  如果视图能处理当前的地域,返回当前视图

         return  (view.checkResource(locale) ? result :  null );

    }

 

     protected  AbstractUrlBasedView buildView(String viewName)  throws  Exception {

         //  根据配置的视图类名实例化视图对象

        AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());

       

         // URL =  前缀  +  逻辑视图名  +  后缀

        view.setUrl(getPrefix() + viewName + getSuffix());

       

         //  设置内容类型

        String contentType = getContentType();

         if  (contentType !=  null ) {

            view.setContentType(contentType);

        }

       

         //  设置请求环境属性的关键字值

        view.setRequestContextAttribute(getRequestContextAttribute());

       

         //  这是静态属性

        view.setAttributesMap(getAttributesMap());

         return  view;

    }

 

}

我们看到,抽象的基于URL的视图解析器返回抽象的基于URL的视图。现在我们分析一下抽象的基于URL的视图的类的实现。实现视图的第一层的类是抽象视图,它合并了派遣器Servlet传进来的模型数据和视图解析器传进来的静态模型数据。将这些数据准备给子类实现视图显示逻辑。如下代码所示,

public   abstract   class  AbstractView  extends  WebApplicationObjectSupport  implements  View, BeanNameAware {

     //  缺省的内容类型为 HTML

     public   static   final  String  DEFAULT_CONTENT_TYPE  = "text/html;charset=ISO-8859-1";

 

     //  每次输出的数据长度

     private   static   final   int   OUTPUT_BYTE_ARRAY_INITIAL_SIZE  = 4096;

 

     //  视图作为 Bean 的名字

     private  String beanName;

 

     //  视图的能处理的内容类型

     private  String contentType =  DEFAULT_CONTENT_TYPE ;

 

     //  保存请求环境属性的关键字

     private  String requestContextAttribute;

 

     //  静态属性集合

     private   final  Map<String, Object> staticAttributes =  new  HashMap<String, Object>();

 

     //  使用 CSV 格式的字符串设置静态属性

     public   void  setAttributesCSV(String propString)  throws  IllegalArgumentException {

         if  (propString !=  null ) {

            StringTokenizer st =  new  StringTokenizer(propString, ",");

             while  (st.hasMoreTokens()) {

                String tok = st.nextToken();

                 int  eqIdx = tok.indexOf("=");

                 if  (eqIdx == -1) {

                     throw   new  IllegalArgumentException("Expected = in attributes CSV string '" + propString + "'");

                }

                 if  (eqIdx >= tok.length() - 2) {

                     throw   new  IllegalArgumentException(

                            "At least 2 characters ([]) required in attributes CSV string '" + propString + "'");

                }

                String name = tok.substring(0, eqIdx);

                String value = tok.substring(eqIdx + 1);

 

                 // Delete first and last characters of value: { and }

                value = value.substring(1);

                value = value.substring(0, value.length() - 1);

 

                addStaticAttribute(name, value);

            }

        }

    }

 

     public   void  render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)  throws  Exception {

         if  (logger.isTraceEnabled()) {

            logger.trace("Rendering view with name '" +  this .beanName + "' with model " + model +

                " and static attributes " +  this .staticAttributes);

        }

 

         // Consolidate static and dynamic model attributes.

        Map<String, Object> mergedModel =

                 new  HashMap<String, Object>( this .staticAttributes.size() + (model !=  null  ? model.size() : 0));

        mergedModel.putAll( this .staticAttributes);

         if  (model !=  null ) {

            mergedModel.putAll(model);

        }

 

         // Expose RequestContext?

         if  ( this .requestContextAttribute !=  null ) {

            mergedModel.put( this .requestContextAttribute, createRequestContext(request, response, mergedModel));

        }

 

         //  准备响应

        prepareResponse(request, response);

       

         //  生成响应

        renderMergedOutputModel(mergedModel, request, response);

    }

 

     //  如果设置了请求环境属性的关键字值,导出请求环境

     protected  RequestContext createRequestContext(

            HttpServletRequest request, HttpServletResponse response, Map<String, Object> model) {

 

         return   new  RequestContext(request, response, getServletContext(), model);

    }

 

     //  解决 IE 的一个 bug

     protected   void  prepareResponse(HttpServletRequest request, HttpServletResponse response) {

         if  (generatesDownloadContent()) {

            response.setHeader("Pragma", "private");

            response.setHeader("Cache-Control", "private, must-revalidate");

        }

    }

 

     //  默认不产生下载内容

     protected   boolean  generatesDownloadContent() {

         return   false ;

    }

 

     //  子类需要实现显示视图的逻辑

     protected   abstract   void  renderMergedOutputModel(

            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws  Exception;

 

     //  提供功能导出模型数据到请求属性中

     protected   void  exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request)  throws  Exception {

         for  (Map.Entry<String, Object> entry : model.entrySet()) {

            String modelName = entry.getKey();

            Object modelValue = entry.getValue();

             if  (modelValue !=  null ) {

                request.setAttribute(modelName, modelValue);

                 if  (logger.isDebugEnabled()) {

                    logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +

                            "] to request in view with name '" + getBeanName() + "'");

                }

            }

             else  {

                request.removeAttribute(modelName);

                 if  (logger.isDebugEnabled()) {

                    logger.debug("Removed model object '" + modelName +

                            "' from request in view with name '" + getBeanName() + "'");

                }

            }

        }

    }

 

     //  创建临时的内存输出流

     protected  ByteArrayOutputStream createTemporaryOutputStream() {

         return   new  ByteArrayOutputStream( OUTPUT_BYTE_ARRAY_INITIAL_SIZE );

    }

 

     //  将一个内存输出流写入响应中

     protected   void  writeToResponse(HttpServletResponse response, ByteArrayOutputStream baos)  throws IOException {

         // Write content type and also length (determined via byte array).

        response.setContentType(getContentType());

        response.setContentLength(baos.size());

 

         // Flush byte array to servlet output stream.

        ServletOutputStream out = response.getOutputStream();

        baos.writeTo(out);

        out.flush();

    }

 

}

视图实现的体系结构中的下一个实现类是抽象的基于URL的视图。它没有实现如何显示URL,而是实现了保存一个URL的值,子类需要根据URL的值来完成视图的显示。如下代码所示,

public   abstract   class  AbstractUrlBasedView  extends  AbstractView  implements  InitializingBean {

     //  存储一个将要显示的 URL

     private  String url;

 

 

     protected  AbstractUrlBasedView() {

    }

 

     protected  AbstractUrlBasedView(String url) {

         this .url = url;

    }

 

     public   void  afterPropertiesSet()  throws  Exception {

         //  默认 URL 是必须的

         if  (isUrlRequired() && getUrl() ==  null ) {

             throw   new  IllegalArgumentException("Property 'url' is required");

        }

    }

 

     protected   boolean  isUrlRequired() {

         return   true ;

    }

 

     //  占位符方法,子类需要判断是否此视图支持某一个地域信息

     public   boolean  checkResource(Locale locale)  throws  Exception {

         return   true ;

    }

 

}

抽象的基于URL的视图解析器和抽象的基于URL的视图是体系结构中的基础抽象类,所有其他的实体类都是通过实现他们来实现一个具体视图解析的逻辑。

4.3.1.1 内部资源视图解析器和内部资源视图

内部资源视图解析器和内部资源视图是最常用的视图解析器和视图的实现。他们通过转发请求给JSP, Servlet或者具有JSTL标记的JSP组件进行处理。

内部资源视图解析器通过改写抽象的基于URL的视图解析器的构建视图方法,构建专用的内部资源视图或者JSTL视图。如下代码所示,

public   class  InternalResourceViewResolver  extends  UrlBasedViewResolver {

 

     //  判断是否 JSTL 相关类存在

     private   static   final   boolean   jstlPresent  = ClassUtils. isPresent (

            "javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver. class .getClassLoader());

 

     //  是否总是使用包含派遣 HTTP 请求

     private  Boolean alwaysInclude;

 

     //  是否导出环境的 Bean 作为属性

     private  Boolean exposeContextBeansAsAttributes;

 

     //  需要导出 Web 应用程序环境中的 Bean 的名字

     private  String[] exposedContextBeanNames;

 

 

     public  InternalResourceViewResolver() {

         //  如果 JSTL 相关类存在,则使用 JSTL 视图,否则使用内部资源视图

        Class viewClass = requiredViewClass();

         if  (viewClass.equals(InternalResourceView. class ) &&  jstlPresent ) {

            viewClass = JstlView. class ;

        }

        setViewClass(viewClass);

    }

 

    @Override

     protected  Class requiredViewClass() {

         //  默认使用内部资源视图

         return  InternalResourceView. class ;

    }

 

    @Override

     protected  AbstractUrlBasedView buildView(String viewName)  throws  Exception {

         //  使用超类创建内部资源视图或者 JSTL 视图

        InternalResourceView view = (InternalResourceView)  super .buildView(viewName);

       

         //  设置是否总是使用包含请求操作

         if  ( this .alwaysInclude !=  null ) {

            view.setAlwaysInclude( this .alwaysInclude);

        }

       

         //  设置是否导出环境的 Bean 作为属性

         if  ( this .exposeContextBeansAsAttributes !=  null ) {

            view.setExposeContextBeansAsAttributes( this .exposeContextBeansAsAttributes);

        }

       

         //  需要导出 Web 应用程序环境中的 Bean 的名字

         if  ( this .exposedContextBeanNames !=  null ) {

            view.setExposedContextBeanNames( this .exposedContextBeanNames);

        }

       

         //  防止派遣死循环

        view.setPreventDispatchLoop( true );

         return  view;

    }

 

}

从上面视图解析器的实现可以看出,它通过检查是否存在JSTL相关类,来创建内部资源视图还是JSTL视图。事实上,JSTL视图是内部资源视图的子类,它不但支持JSP和Servlet服务器断组件,还支持包含JSTL的JSP展示层的组件。如下代码所示,

public   class  InternalResourceView  extends  AbstractUrlBasedView {

     //  是否总是使用包含派遣请求

     private   boolean  alwaysInclude =  false ;

 

     //  是否导出转发属性

     private   volatile  Boolean exposeForwardAttributes;

 

     //  是否导出应用程序环境中的 Bean 作为属性

     private   boolean  exposeContextBeansAsAttributes =  false ;

 

     //  导出那些 Bean 作为属性

     private  Set<String> exposedContextBeanNames;

 

     //  是否阻止派遣死循环

     private   boolean  preventDispatchLoop =  false ;

 

    @Override

     protected   void  initServletContext(ServletContext sc) {

         //  如果早于 Servlet 2.5 ,需要手动导出转发属性

         if  ( this .exposeForwardAttributes ==  null  && sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {

             this .exposeForwardAttributes = Boolean. TRUE ;

        }

    }

 

    @Override

     protected   void  renderMergedOutputModel(

            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws  Exception {

 

         // Determine which request handle to expose to the RequestDispatcher.

        HttpServletRequest requestToExpose = getRequestToExpose(request);

 

         // Expose the model object as request attributes.

        exposeModelAsRequestAttributes(model, requestToExpose);

 

         // Expose helpers as request attributes, if any.

        exposeHelpers(requestToExpose);

 

         // Determine the path for the request dispatcher.

        String dispatcherPath = prepareForRendering(requestToExpose, response);

 

         // Obtain a RequestDispatcher for the target resource (typically a JSP).

        RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);

         if  (rd ==  null ) {

             throw   new  ServletException("Could not get RequestDispatcher for [" + getUrl() +

                    "]: Check that the corresponding file exists within your web application archive!");

        }

 

         // If already included or response already committed, perform include, else forward.

         if  (useInclude(requestToExpose, response)) {

            response.setContentType(getContentType());

             if  (logger.isDebugEnabled()) {

                logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");

            }

            rd.include(requestToExpose, response);

        }

 

         else  {

             // Note: The forwarded resource is supposed to determine the content type itself.

            exposeForwardRequestAttributes(requestToExpose);

             if  (logger.isDebugEnabled()) {

                logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '"+ getBeanName() + "'");

            }

            rd.forward(requestToExpose, response);

        }

    }

 

     protected  HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) {

         //  如果导出 Bean 作为属性,则创建一个代理 HTTP Servlet 请求,这个代理请求包含导出的 Bean 作为属性

         if  ( this .exposeContextBeansAsAttributes ||  this .exposedContextBeanNames !=  null ) {

             return   new  ContextExposingHttpServletRequest(

                    originalRequest, getWebApplicationContext(),  this .exposedContextBeanNames);

        }

         //  否则导出原来的 HTTP Servlet 请求

         return  originalRequest;

    }

   

     protected  String prepareForRendering(HttpServletRequest request, HttpServletResponse response)

             throws  Exception {

 

        String path = getUrl();

         //  如果设置了防止派遣死循环,并且检测到派遣到最初的 URL ,则阻止死循环,抛出异常,终止处理

         if  ( this .preventDispatchLoop) {

            String uri = request.getRequestURI();

             if  (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {

                 throw   new  ServletException("Circular view path [" + path + "]: would dispatch back " +

                        "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +

                        "(Hint: This may be the result of an unspecified view, due to default view name generation.)");

            }

        }

         return  path;

    }

 

     protected  RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {

         //  使用容器的请求派遣器

         return  request.getRequestDispatcher(path);

    }

 

     protected   boolean  useInclude(HttpServletRequest request, HttpServletResponse response) {

         //  如果设置了总是使用包含,或者请求是包含请求,或者已经发送了响应代码

         return  ( this .alwaysInclude || WebUtils.isIncludeRequest(request) || response.isCommitted());

    }

 

     protected   void  exposeForwardRequestAttributes(HttpServletRequest request) {

         if  ( this .exposeForwardAttributes !=  null  &&  this .exposeForwardAttributes) {

             try  {

                 //  导出转发请求属性 FORWARD_REQUEST_URI_ATTRIBUTE, FORWARD_CONTEXT_PATH_ATTRIBUTE, FORWARD_SERVLET_PATH_ATTRIBUTE, FORWARD_PATH_INFO_ATTRIBUTE, FORWARD_QUERY_STRING_ATTRIBUTE

                WebUtils.exposeForwardRequestAttributes(request);

            }

             catch  (Exception ex) {

                 // Servlet container rejected to set internal attributes, e.g. on TriFork.

                 this .exposeForwardAttributes = Boolean. FALSE ;

            }

        }

    }

 

}

子类JSTL视图能够重用了所有的内部资源视图的逻辑,而且增加了导出本地化环境的实现。如下代码所示,

public   class  JstlView  extends  InternalResourceView {

     //  设置的消息源

     private  MessageSource messageSource;

 

    @Override

     protected   void  exposeHelpers(HttpServletRequest request)  throws  Exception {

         //  如果设置了消息源,则导出设置的消息源

         if  ( this .messageSource !=  null ) {

            JstlUtils.exposeLocalizationContext(request,  this .messageSource);

        }

         //  否则导出应用程序环境作为消息源,应用程序环境也实现了消息源接口

         else  {

            JstlUtils.exposeLocalizationContext( new  RequestContext(request, getServletContext()));

        }

    }

 

}

4.3.1.2 瓦块视图解析器和瓦块视图

瓦块视图解析器和瓦块视图把HTTP请求派遣给瓦块容器进行处理,瓦块容器使用不同的瓦块定义来显示一个完整的瓦块页面。

瓦块视图解析器继承自抽象的基于URL的视图解析器。简单的通过返回瓦块视图类来实现的。如下代码所示,

public   class  TilesViewResolver  extends  UrlBasedViewResolver {

 

     public  TilesViewResolver() {

        setViewClass(requiredViewClass());

    }

 

    @Override

     protected  Class requiredViewClass() {

         //  简单的返回瓦块视图的定义

         return  TilesView. class ;

    }

 

}

瓦块视图则把当前的URL交给瓦块容器进行响应。如下代码注释,

public   class  TilesView  extends  AbstractUrlBasedView {

 

    @Override

     public   boolean  checkResource( final  Locale locale)  throws  Exception {

         //  从应用程序对象中取得瓦块容器

        TilesContainer container = ServletUtil.getContainer(getServletContext());

         //  瓦块容器应该是基本瓦块容器类型,如果不是,则做乐观处理

         if  (!(container  instanceof  BasicTilesContainer)) {

             // Cannot check properly - let's assume it's there.

             return   true ;

        }

       

         //  创建瓦块请求环境

        BasicTilesContainer basicContainer = (BasicTilesContainer) container;

        TilesApplicationContext appContext =  new ServletTilesApplicationContext(getServletContext());

        TilesRequestContext requestContext =  new  ServletTilesRequestContext(appContext,  null null ) {

            @Override

             public  Locale getRequestLocale() {

                 return  locale;

            }

        };

       

         //  检查是否存在瓦块页面定义可以处理当前的 URL

         return  (basicContainer.getDefinitionsFactory().getDefinition(getUrl(), requestContext) != null );

    }

 

    @Override

     protected   void  renderMergedOutputModel(

            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws  Exception {

 

        ServletContext servletContext = getServletContext();

         //  从应用程序对象中取得瓦块容器

        TilesContainer container = ServletUtil.getContainer(servletContext);

         //  如果瓦块容器不存在,则不能处理当前的请求,终止处理

         if  (container ==  null ) {

             throw   new  ServletException("Tiles container is not initialized. " +

                    "Have you added a TilesConfigurer to your web application context?");

        }

 

         //  导出模型映射数据作为请求属性以供瓦块容器使用

        exposeModelAsRequestAttributes(model, request);

       

         //  导出本地化环境

        JstlUtils.exposeLocalizationContext( new  RequestContext(request, servletContext));

 

         if  (!response.isCommitted()) {

             // Tiles is going to use a forward, but some web containers (e.g. OC4J 10.1.3)

             // do not properly expose the Servlet 2.4 forward request attributes... However,

             // must not do this on Servlet 2.5 or above, mainly for GlassFish compatibility.

            ServletContext sc = getServletContext();

             if  (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {

                WebUtils.exposeForwardRequestAttributes(request);

            }

        }

 

         //  把请求交给瓦块容器进行处理

        container.render(getUrl(), request, response);

    }

 

}

4.3.1.3 模板视图解析器和模板视图

模板视图解析器和模板视图把HTTP请求传递给模板技术的容器进行处理,它支持Velocity和FreeMaker。

抽象的模板视图解析器继承自基于URL的视图解析器。并且可以对创建的抽象模板视图进行设置,如下代码注释,

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;

    }

 

}

抽象的模板视图根据客户化的设置对请求进行一系列的预处理,然后,把HTTP请求传递给子类,子类根据具体的模板实现技术交给模板容器进行处理。

public   abstract   class  AbstractTemplateView  extends  AbstractUrlBasedView {

 

     public   static   final  String  SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE  ="springMacroRequestContext";

 

 

     private   boolean  exposeRequestAttributes =  false ;

 

     private   boolean  allowRequestOverride =  false ;

 

     private   boolean  exposeSessionAttributes =  false ;

 

     private   boolean  allowSessionOverride =  false ;

 

     private   boolean  exposeSpringMacroHelpers =  true ;

 

    @Override

     protected   final   void  renderMergedOutputModel(

            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws  Exception {

 

         if  ( this .exposeRequestAttributes) {

             //  导出请求属性到 Spring 模型中

             for  (Enumeration en = request.getAttributeNames(); en.hasMoreElements();) {

                String attribute = (String) en.nextElement();

                 if  (model.containsKey(attribute) && ! this .allowRequestOverride) {

                     throw   new  ServletException("Cannot expose request attribute '" + attribute +

                        "' because of an existing model object of the same name");

                }

                Object attributeValue = request.getAttribute(attribute);

                 if  (logger.isDebugEnabled()) {

                    logger.debug("Exposing request attribute '" + attribute +

                            "' with value [" + attributeValue + "] to model");

                }

                model.put(attribute, attributeValue);

            }

        }

 

         if  ( this .exposeSessionAttributes) {

             //  导出 Session 属性到 Spring 模型中

            HttpSession session = request.getSession( false );

             if  (session !=  null ) {

                 for  (Enumeration en = session.getAttributeNames(); en.hasMoreElements();) {

                    String attribute = (String) en.nextElement();

                     if  (model.containsKey(attribute) && ! this .allowSessionOverride) {

                         throw   new  ServletException("Cannot expose session attribute '" + attribute +

                            "' because of an existing model object of the same name");

                    }

                    Object attributeValue = session.getAttribute(attribute);

                     if  (logger.isDebugEnabled()) {

                        logger.debug("Exposing session attribute '" + attribute +

                                "' with value [" + attributeValue + "] to model");

                    }

                    model.put(attribute, attributeValue);

                }

            }

        }

 

         if  ( this .exposeSpringMacroHelpers) {

             if  (model.containsKey( SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE )) {

                 throw   new  ServletException(

                        "Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE  +

                        "' because of an existing model object of the same name");

            }

             // Expose RequestContext instance for Spring macros.

            model.put( SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE ,

                     new  RequestContext(request, response, getServletContext(), model));

        }

 

         //  应用响应内容类型

        applyContentType(response);

 

         //  由子类实现使用模板技术进行处理

        renderMergedTemplateModel(model, request, response);

    }

 

     protected   void  applyContentType(HttpServletResponse response)   {

         if  (response.getContentType() ==  null ) {

            response.setContentType(getContentType());

        }

    }

 

     protected   abstract   void  renderMergedTemplateModel(

            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws  Exception;

 

}

抽象模板视图解析器和抽象模板视图有两个类型的子类,两个类型的子类各自支持Velocity和FreeMaker。

FreeMakerViewResolver的实现非常简单,它仅仅返回FreeMakerView类。而FreeMakerView则将HTTP请求传递给FreeMaker容器来处理并且响应产生展示层的界面。

VelocityViewResolver的实现非常简单,它仅仅返回VelocityView类。而VelocityView则将HTTP请求传递给Velocity容器来处理并且响应产生展示层的界面。

VelocityLayoutViewResolver和VelocityLayoutView是VelocityViewResolver和VelocityView的子类,它增加了对Layout的支持。

既然这些类的实现需要更多的FreeMaker和Velocity的知识,我们这里不做详细的代码分析和注释。

4.3.1.4 Jasper报表视图解析器和Jaspter报表视图

由于本人时间有限,将在本书第二版完成本节的内容。如您愿意提供帮助请发邮件到 robertleepeak@gmail.com

4.3.1.5 XSLT视图解析器和XSLT视图

XSLT视图解析器和XSLT视图使用XSLT技术将XML DOM数据转换成为一种支持的其他数据格式,这包括其他的XML格式,也包括类似HTML的展示层的数据显示格式。

XSLT视图解析器继承自基于URL的视图解析器,并且返回XSLT视图。如下代码注释,

public   class  XsltViewResolver  extends  UrlBasedViewResolver {

 

     private  String sourceKey;

 

     private  URIResolver uriResolver;

 

     private  ErrorListener errorListener;

 

     private   boolean  indent =  true ;

 

     private  Properties outputProperties;

 

     private   boolean  cacheTemplates =  true ;

 

    @Override

     protected  Class requiredViewClass() {

         //  返回 XSLT 视图类,父类负责创建此类实例

         return  XsltView. class ;

    }

 

    @Override

     protected  AbstractUrlBasedView buildView(String viewName)  throws  Exception {

         //  调用父类创建 XSLT 视图类的实例

        XsltView view = (XsltView)  super .buildView(viewName);

       

         //  设置数据源的属性关键字

        view.setSourceKey( this .sourceKey);

       

         //  设置 URI 解析器

         if  ( this .uriResolver !=  null ) {

            view.setUriResolver( this .uriResolver);

        }

       

         //  设置错误监听器

         if  ( this .errorListener !=  null ) {

            view.setErrorListener( this .errorListener);

        }

       

         //  设置其他属性

        view.setIndent( this .indent);

        view.setOutputProperties( this .outputProperties);

        view.setCacheTemplates( this .cacheTemplates);

         return  view;

    }

 

}

XSLT视图则使用XSLT技术将一种XML格式的数据源转换成为另外一种,如下代码注释所示,

public   class  XsltView  extends  AbstractUrlBasedView {

     //  可以配置一个非缺省的转换工厂类

     private  Class transformerFactoryClass;

 

     private  String sourceKey;

 

     private  URIResolver uriResolver;

 

     private  ErrorListener errorListener =  new  SimpleTransformErrorListener(logger);

 

     private   boolean  indent =  true ;

 

     private  Properties outputProperties;

 

     private   boolean  cacheTemplates =  true ;

 

     private  TransformerFactory transformerFactory;

 

     private  Templates cachedTemplates;

 

    @Override

     protected   void  initApplicationContext()  throws  BeansException {

         //  创建转换工厂类的实例并且初始化

         this .transformerFactory = newTransformerFactory( this .transformerFactoryClass);

         this .transformerFactory.setErrorListener( this .errorListener);

         if  ( this .uriResolver !=  null ) {

             this .transformerFactory.setURIResolver( this .uriResolver);

        }

         //  如果缓存模板,则初始化时加载所有模板

         if  ( this .cacheTemplates) {

             this .cachedTemplates = loadTemplates();

        }

    }

 

     protected  TransformerFactory newTransformerFactory(Class transformerFactoryClass) {

         //  如果配置了非缺省的转换工厂类,则实例化配置的转换工厂类

         if  (transformerFactoryClass !=  null ) {

             try  {

                 return  (TransformerFactory) transformerFactoryClass.newInstance();

            }

             catch  (Exception ex) {

                 throw   new  TransformerFactoryConfigurationError(ex, "Could not instantiate TransformerFactory");

            }

        }

         //  否则使用缺省的转换工厂类

         else  {

             return  TransformerFactory.newInstance();

        }

    }

 

    @Override

     protected   void  renderMergedOutputModel(

            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)

             throws  Exception {

         //  如果没有缓存 XSLT 模板,则加载模板

        Templates templates =  this .cachedTemplates;

         if  (templates ==  null ) {

            templates = loadTemplates();

        }

 

         //  根据模板穿件转换对象

        Transformer transformer = createTransformer(templates);

         //  配置转换对象

        configureTransformer(model, response, transformer);

         //  配置响应

        configureResponse(model, response, transformer);

        Source source =  null ;

         try  {

             //  找到 XSLT 的数据源

            source = locateSource(model);

             if  (source ==  null ) {

                 throw   new  IllegalArgumentException("Unable to locate Source object in model: " + model);

            }

             //  进行事实的转换

            transformer.transform(source, createResult(response));

        }

         finally  {

            closeSourceIfNecessary(source);

        }

    }

 

     protected  Result createResult(HttpServletResponse response)  throws  Exception {

         //  使用响应的输出流构造数据结果

         return   new  StreamResult(response.getOutputStream());

    }

 

     protected  Source locateSource(Map<String, Object> model)  throws  Exception {

         //  如果配置了数据源的关键字,则使用模型属性中以此关键字标记的属性

         if  ( this .sourceKey !=  null ) {

             return  convertSource(model.get( this .sourceKey));

        }

       

         //  否则找到第一个能够作为数据源类型的属性作为数据源

        Object source = CollectionUtils.findValueOfType(model.values(), getSourceTypes());

         return  (source !=  null  ? convertSource(source) :  null );

    }

 

     protected  Class[] getSourceTypes() {

         //  这些类型都可以作为输入的数据源

         return   new  Class[] {Source. class , Document. class , Node. class , Reader. class , InputStream. class , Resource. class };

    }

 

     protected  Source convertSource(Object source)  throws  Exception {

         //  需要把不同类型的数据源转换为 XSLT 的标准数据源类型

         if  (source  instanceof  Source) {

             return  (Source) source;

        }

         else   if  (source  instanceof  Document) {

             return   new  DOMSource(((Document) source).getDocumentElement());

        }

         else   if  (source  instanceof  Node) {

             return   new  DOMSource((Node) source);

        }

         else   if  (source  instanceof  Reader) {

             return   new  StreamSource((Reader) source);

        }

         else   if  (source  instanceof  InputStream) {

             return   new  StreamSource((InputStream) source);

        }

         else   if  (source  instanceof  Resource) {

            Resource resource = (Resource) source;

             return   new  StreamSource(resource.getInputStream(), resource.getURI().toASCIIString());

        }

         else  {

             throw   new  IllegalArgumentException("Value '" + source + "' cannot be converted to XSLT Source");

        }

    }

 

     protected   void  configureTransformer(Map<String, Object> model, HttpServletResponse response, Transformer transformer) {

         //  把模型参数拷贝到 XSLT 转换对象中

        copyModelParameters(model, transformer);

         //  拷贝输出属性

        copyOutputProperties(transformer);

         //  配置是否缩进

        configureIndentation(transformer);

    }

 

     protected   final   void  configureIndentation(Transformer transformer) {

         //  打开或者关闭缩进

         if  ( this .indent) {

            TransformerUtils.enableIndenting(transformer);

        }

         else  {

            TransformerUtils.disableIndenting(transformer);

        }

    }

 

     protected   final   void  copyOutputProperties(Transformer transformer) {

         //  拷贝输出属性

         if  ( this .outputProperties !=  null ) {

            Enumeration en =  this .outputProperties.propertyNames();

             while  (en.hasMoreElements()) {

                String name = (String) en.nextElement();

                transformer.setOutputProperty(name,  this .outputProperties.getProperty(name));

            }

        }

    }

 

     protected   final   void  copyModelParameters(Map<String, Object> model, Transformer transformer) {

         //  把模型参数拷贝到 XSLT 转换对象中

         for  (Map.Entry<String, Object> entry : model.entrySet()) {

            transformer.setParameter(entry.getKey(), entry.getValue());

        }

    }

 

     protected   void  configureResponse(Map<String, Object> model, HttpServletResponse response, Transformer transformer) {

        String contentType = getContentType();

        String mediaType = transformer.getOutputProperty(OutputKeys.MEDIA_TYPE);

        String encoding = transformer.getOutputProperty(OutputKeys.ENCODING);

         if  (StringUtils.hasText(mediaType)) {

            contentType = mediaType;

        }

         if  (StringUtils.hasText(encoding)) {

             // Only apply encoding if content type is specified but does not contain charset clause already.

             if  (contentType !=  null  && !contentType.toLowerCase().contains(WebUtils.CONTENT_TYPE_CHARSET_PREFIX)) {

                contentType = contentType + WebUtils.CONTENT_TYPE_CHARSET_PREFIX + encoding;

            }

        }

         //  根据 XSLT 模板支持的类型设置响应的内容类型

        response.setContentType(contentType);

    }

 

     private  Templates loadTemplates()  throws  ApplicationContextException {

         //  取得模板的数据源

        Source stylesheetSource = getStylesheetSource();

         try  {

             //  根据模板的数据源创建模板对象

            Templates templates =  this .transformerFactory.newTemplates(stylesheetSource);

             if  (logger.isDebugEnabled()) {

                logger.debug("Loading templates '" + templates + "'");

            }

             return  templates;

        }

         catch  (TransformerConfigurationException ex) {

             throw   new  ApplicationContextException("Can't load stylesheet from '" + getUrl() + "'", ex);

        }

         finally  {

            closeSourceIfNecessary(stylesheetSource);

        }

    }

   

     protected  Transformer createTransformer(Templates templates)  throws TransformerConfigurationException {

         //  根据模板创建转换器

        Transformer transformer = templates.newTransformer();

         if  ( this .uriResolver !=  null ) {

            transformer.setURIResolver( this .uriResolver);

        }

         return  transformer;

    }

 

     protected  Source getStylesheetSource() {

         //  根据请求的 URL 创建数据源

        String url = getUrl();

         if  (logger.isDebugEnabled()) {

            logger.debug("Loading XSLT stylesheet from '" + url + "'");

        }

         try  {

            Resource resource = getApplicationContext().getResource(url);

             return   new  StreamSource(resource.getInputStream(), resource.getURI().toASCIIString());

        }

         catch  (IOException ex) {

             throw   new  ApplicationContextException("Can't load XSLT stylesheet from '" + url + "'", ex);

        }

    }

 

     private   void  closeSourceIfNecessary(Source source) {

         //  关闭一个流类型的数据源

         if  (source  instanceof  StreamSource) {

            StreamSource streamSource = (StreamSource) source;

             if  (streamSource.getReader() !=  null ) {

                 try  {

                    streamSource.getReader().close();

                }

                 catch  (IOException ex) {

                     // ignore

                }

            }

             if  (streamSource.getInputStream() !=  null ) {

                 try  {

                    streamSource.getInputStream().close();

                }

                 catch  (IOException ex) {

                     // ignore

                }

            }

        }

    }

 

}

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