解读Tapestry5.1——页面渲染

本文旨在介绍Tapestry5.1的页面渲染(render)过程,希望最终能给出一个完整清晰的页面渲染流程,以便大家能更好的理解 Tapestry页面渲染的过程,构架出更漂亮的Tapestry组件。

页面渲染简单的说就是生产HTML页面,响应输入的HTTP请求。在我另一篇博文中简要的谈到了http请求转化为页面渲染处理的机制和页面渲染的 流程,本文将继续详细介绍页面渲染的流程,有关请求处理流程的细节可以参见《解读Tapestry5.1——请求调用链 》。

为了描述清楚整个渲染过程,在介绍渲染细节之前,我首先会简要介绍渲染的基 本概念 ,然后介绍Tapestry组件与框架在渲染过程中的职 责划分 ,以理清渲染过程中组件与框架的关系。最后,我将详细讨论组 件职责框 架职责 。对于组件,将着重说明它的生 命周期 ,而对于框架则着重说明它是如何支持这一声明周期的,主要包括:环 境构造调 度策略中 间结构 这几部分。

1. 基本概念

首先,我们回顾一下jsp是如何渲染出一个HTML文档的。当一个请求到达jsp页面时,jsp引擎会解析jsp文件,生成一个servlet类。 文件中静态的部分会原样输出,而动态的部分则作为程序代码看待。

那么Tapestry的渲染思路呢?

本质上,Tapestry还是遵循了这一思路,静态的文本原样输出,而动态的部分则视为组件。

那么组件又是什么呢?

在介绍Tapestry之前,我们先看看java的swing控件,Tapestry的组件和swing控件是由一定的相似性的。我们可以从三个方 面考察Swing控件,一个是UI展示,一个是数据模型,一个是事件处理。Swing控件统一处理了这三个方面的内容,具体的说,它既负责UI的显示,又 负责处理输入的数据模型,还会产生相应的事件交由外部处理。在使用组件时,只需要考虑如何提供数据并处理事件,并不用关系展示和监听用户输入(比如鼠标动 作等)的细节。

Tapestry的组件也是这样组织的。UI、数据和事件这三者有机的统一,使用组件只需要考虑如何提供数据,并处理响应事件即可。对于如何生成 HTML片段,如何处理HTTP请求细节,如何接收用户输入等等都不必考虑。

为了能更好的支持这三者的统一,Tapestry将其分别划分到渲染和事件着两个阶段完成。

我们也可以拿jsp与着两个阶段做类比,回想下写过的jsp程序,其中有一部分代码我们是用来做显示的,而有一部分是在用户触发一些事件后,响应用 户请求的。

Tapestry将其划分为两个阶段正好有效的分离的这两部分的代码。接下来本文将详细讨论渲染的过程,而有关事件的处理可以参见我另几篇篇博文《解读Tapestry5.1——请求调用链 》、《Tapestry5 事件分派机制 》和《解读Tapestry5.1——Form 》。

2.职责划分

渲染需要处理的问题相当多,比方说生成各类HTML标签用于显示页面,生成用于响应用户事件的URL以便能将请求传回给组件处理,处理其它页面传入 的参数,自动引入组件使用到的js或者css文件,等等。

那么,如此多的问题都要如何处理呢?换句话说,哪些职责由组件需要负责?哪些职责又由框架提供呢?

这正是本文试图说明的问题。

简单的说,框架承担了调度和管理者的职责,而组件负责执行与实施的职责。

详细点讲,框架需要提供资源管理的机制,提供URL生成、编码与解编码的方式,提供组件渲染所需的生命周期的支持,等底层细节操作。而组件则是负责 在框架的调度下,使用这些服务,生成对应HTML标签。

下面,我将分组件和框架两部分讨论各自的职责。

3.组件职责

对于使用Tapestry进行Web开发而言,最重要的,最频繁接触的是组件,而最终展示给用户的页面也是一个特殊的组件。所以,要了解页面渲染的 过程,首先需要了解组件的职责。

我们先回忆一下曾经写过的页面,里面除了正常的HTML标签外,还有很多用于做循环和选择的控制逻辑。因为页面很多情况下并不是一成不变的,而是根 据不同的状态,有选择性的展现出不同的样式,比如显示table的时候总会循环的生成tr,再比如对于等于用户我们需要显示用户名,而对于非登录用户我们 需要显示登录框。

所以,组件按期职责可以分为两类:显示组件和控制组件。

显示组件负责生成一段与这个组件相关的HTML代码片段,而控制组件则类似于程序的控制逻辑,控制选择和循环操作。实际上,还可以分出一类交互性组 件,但交互性组件和显示组件在渲染时的行为是相似的,只是多了一个事件处理,可以看作一类特殊的显示组件。不过,很多时候组件的界限并不一定非常明显,它 可能即会生成一段HTML代码,又会控制页面的逻辑,我们这种划分只是为了能更好的说明组件的职责。

通过这样的划分不难看出,组件的主要职责是生成HTML代码与控制渲染流程。

下面一节我们将继续讨论组件是如何履行这些职责的。

3.1生命周期

Tapestry框架为组件定义了一个标准的生命周期,所以要了解组件是如何履行其职责的,就必须先了解组件的生命周期。因为,不论是哪一类组件, 实现上并没有明显的区别。它们都会按照一定的生命周期执行,所不同的是,渲染组件会在生命周期执行过程中生成HTML代码,而控制组件只会在生命周期中改 变它所包含组件的渲染流程。

组件渲染生命周期

上图给出了一个组件的标准渲染过程,定义了10个主要的执行阶段,可以看出每个阶段都有两条边输出边,分别对应着true和false两个选择。组 件执行过程中,每个阶段都可以返回一个boolean值,决定下一步的执行路径(对于返回值为void的函数,默认为true)。

我们再回头看看显示组件,它们一般不会改变组件生命周期的顺序,每个阶段都会选择true的那条边往下走,并在适当的阶段输出HTML代码片段。

比如一个PageLink组件(用于生成指向其它页面的链接的组件),它会在Begin Render阶段输出一段类似于"<a href='page link'>"的代码,并在After Render阶段输出结束标签“</a>”,从而形成一个完整的<a>标签。而在<a>标签之间的文字或图片则是在 Render Body阶段完成。有一点值得注意, href 属性值是通过调用到Tapestry服务实现的,并非组件自己构造。

对于控制组件则恰好相反,它们会在不同的阶段通过返回不同的值来控制页面的显示流程,但并不会生成HTML代码片段。

比如 If 组件(用于控制其所包含组件是否显示的组件),如果允许其内嵌的组件显示,则会在Begin Render阶段返回true,这样最终就会进入Render Body阶段,显示其内嵌的组件。再比如Loop组件(用于循环显示的组件),如果循环没有结束,它会在After Render阶段返回false,这样该组件又会重新回到BeginRender阶段,并重新经过Render Body阶段,使得其内嵌的组件可以循环的被执行。

总的来说,组件实现其职责的大致思路是分阶段的处理整个渲染流程。

下面我们看看最重要的几个阶段:Setup Render, Begin Render, Render Template, Render Body,After Render,和Cleanup Render。

Setup Render 阶段主要用于渲染前的准备操作,比如初始化一些数据;

Begin Render 主要用于生成一个HTML的开始标签,或者控制渲染流程;

Render Template 主要用于渲染模板中的内容(页面和组件都可以具有模板)。这个阶段一般会有多个组件需要渲染,每进入一个组件,都会依次调用这个组件的各个生命周期阶段, 也就是说,此处是一个从上层组件到其下层组件的一个递归入口,只有其下层组件渲染结束,该组件才会继续其后续阶段的操作。

Render Body 与Render Template相似,也是渲染一段模板中的内容,所不同的是模板是组件和页面所具有的tml文件,而是组件被使用时,内嵌在组件中的一小段模板片段。比 如对于pagelink组件(<a t:type="pagelink" page="index">this is body</a>),它的body是“this is body”这几个文字。虽然对于模板的概念与Render Tamplate略有区别,但是它同样也是一个递归的入口。

After Render 阶段一般用于生成一个结束标签,或者控制其内嵌组件的执行流程。

Cleanup Render 这个阶段一般用于清除数据,但是由于框架会自动的清空页面的属性,所以一般不需要自己清除状态。除非你使用了一些特殊的资源需要清除。

其它几个阶段并非经常用到,更多是保证生命周期的完整性,必要情况下,也可以生成HTML代码或修改组件的执行流程。

4. 框架职责

现在,渲染一个页面,组件要做什么我们已经清楚了,接下来就到了讲述框架职责的阶段了。因为所有的底层服务,以及组件的调度都是由框架负责的。所 以,要深入了解渲染的过程,仅了解组件的职责还是不够的。下面,我们首先了解下渲染的流程。

当一个请求实际到达渲 染处理器 时,该处理器首先会向目标页面发出一个activate事件,为页面提供一个准备渲染,并处理传入参数的机会。

之后,渲染流程就会传入一个 org.apache.tapestry5.internal.services.PageResponseRenderer服务,该服务会实际的调度页 面渲染。

渲染过程大致如下:

首先,获得页面类型(content type),默认为"text/html";然后,根据这一类型生产一个输出器org.apache.tapestry5.MarkupWriter,组 件若有需要输出HTML片段,则会输出到这个输出器中;其 后,org.apache.tapestry5.internal.services.PageMarkupRenderer这一渲染器将会开始调度组件 开始执行其生命周期;最后,MarkupWriter生成的中间结构会实际的输出到返回给客户端的输出流中。

获得页面类型并没有复杂的流程,所以本文就不详细介绍了。而MarkupWriter和最后的中间结构又是紧密相关的,所以将它们并在中间结构这部 分讲述。剩下的核心环节页面渲染器(PageMarkupRenderer)将进一步分为环境构造,调度策略两个环节介绍。

接下来,我将就环 境构造调 度策略中 间结构 这三个部分详细讲述。

但这之前,我想插入一个话题,也就是与activate对应的passivate事件是何时被激发的?

我们知道activate是用于给页面一个初始化的机会,其它页面跳转过来时传递的参数就是通过这个事件让目标页面获取的。

那么passivate呢?passivate是同一个页面渲染结束时,希望传入下一次渲染的参数。一个典型的用途是在Form提交事件处理完之 后,传递参数给接下来的渲染使用。因为Tapestry默认的会在Form提交后,通过重定向渲染页面。也就是说渲染实际上是一个新的请求,Form提交 事件里生成的数据,都被清空了。为了让渲染可以获得Form处理事件产生的数据,一个不用消耗存储空间(session)的方式就是在passivate 事件中返回所需的数据。因为这个数据会保存在URL里面,在下一次请求到达服务器时传递到activate这个事件中。

所以,为了保证passivate返回的数据能传到activate事件,就必须保证不管通过何种方式,链接也好,Form也好,重定向也好,传递 到服务上的URL中都保存有passivate返回的数据。

因此,为了保证数据的可回传性,passivate事件并不是在渲染的某个阶段被激发,而是在生产链接的服务里被激发。由于页面所有的链接都是通过 这个服务生产,所以数据就肯定可以回传到activate事件。实际上,这个时候有一件事情就需要注意了,passivate的事件处理函数最好能缓存一 下需要返回的数据,不要每次都构造。

好了,现在框架的大致流程已经介绍完了,下面进入渲染的正题,即框架是如何构造一个渲染的环境并调度组件执行。

4.1环境构造

环境构造是页面渲染器(PageMarkupRenderer)的一部分,对应于 org.apache.tapestry5.services.MarkupRenderer这个服务。Tapestry使用了其一贯的风格,采用一个管 道了组织这个服务,这个管道的配置代码如下:

  1. public   void  contributeMarkupRenderer(OrderedConfiguration<MarkupRendererFilter> configuration,  
  2.                                          @Symbol (SymbolConstants.PRODUCTION_MODE)  
  3.                                          final   boolean  productionMode,  
  4.                                          @Path ( "${tapestry.spacer-image}" )  
  5.                                          final  Asset spacerImage,  
  6.                                          @Symbol (SymbolConstants.OMIT_GENERATOR_META)  
  7.                                          final   boolean  omitGeneratorMeta,  
  8.                                          @Inject   @Symbol (SymbolConstants.TAPESTRY_VERSION)  
  9.                                          final  String tapestryVersion,  
  10.                                          @Symbol (SymbolConstants.COMBINE_SCRIPTS)  
  11.                                          final   boolean  combineScripts,  
  12.                                          final  SymbolSource symbolSource,  
  13.                                          final  AssetSource assetSource,  
  14.                                          final  ClientDataEncoder clientDataEncoder,  
  15.                                          final  ClientInfrastructure clientInfrastructure)  
  16.     {  
  17.         MarkupRendererFilter documentLinker = new  MarkupRendererFilter()  
  18.         {  
  19.             public   void  renderMarkup(MarkupWriter writer, MarkupRenderer renderer)  
  20.             {  
  21.                 DocumentLinkerImpl linker = new  DocumentLinkerImpl(productionMode,  
  22.                                                                    omitGeneratorMeta,  
  23.                                                                    tapestryVersion,  
  24.                                                                    combineScripts,  
  25.                                                                    request.getContextPath(),  
  26.                                                                    clientDataEncoder);  
  27.                 environment.push(DocumentLinker.class , linker);  
  28.                 renderer.renderMarkup(writer);  
  29.                 environment.pop(DocumentLinker.class );  
  30.                 linker.updateDocument(writer.getDocument());  
  31.             }  
  32.         };  
  33.         MarkupRendererFilter renderSupport = new  MarkupRendererFilter()  
  34.         {  
  35.             public   void  renderMarkup(MarkupWriter writer, MarkupRenderer renderer)  
  36.             {  
  37.                 DocumentLinker linker = environment.peekRequired(DocumentLinker.class );  
  38.                 RenderSupportImpl support = new  RenderSupportImpl(linker, symbolSource, assetSource,  
  39.                                                                   clientInfrastructure);  
  40.                 environment.push(RenderSupport.class , support);  
  41.                 renderer.renderMarkup(writer);  
  42.                 environment.pop(RenderSupport.class );  
  43.                 support.commit();  
  44.             }  
  45.         };  
  46.         MarkupRendererFilter injectDefaultStylesheet = new  MarkupRendererFilter()  
  47.         {  
  48.             public   void  renderMarkup(MarkupWriter writer, MarkupRenderer renderer)  
  49.             {  
  50.                 RenderSupport renderSupport = environment.peek(RenderSupport.class );  
  51.                 for  (Asset stylesheet : clientInfrastructure.getStylesheetStack())  
  52.                 {  
  53.                     renderSupport.addStylesheetLink(stylesheet, null );  
  54.                 }  
  55.                 renderer.renderMarkup(writer);  
  56.             }  
  57.         };  
  58.         MarkupRendererFilter clientBehaviorSupport = new  MarkupRendererFilter()  
  59.         {  
  60.             public   void  renderMarkup(MarkupWriter writer, MarkupRenderer renderer)  
  61.             {  
  62.                 RenderSupport renderSupport = environment.peekRequired(RenderSupport.class );  
  63.                 ClientBehaviorSupportImpl clientBehaviorSupport = new  ClientBehaviorSupportImpl(renderSupport);  
  64.                 environment.push(ClientBehaviorSupport.class , clientBehaviorSupport);  
  65.                 renderer.renderMarkup(writer);  
  66.                 environment.pop(ClientBehaviorSupport.class );  
  67.                 clientBehaviorSupport.commit();  
  68.             }  
  69.         };  
  70.         MarkupRendererFilter heartbeat = new  MarkupRendererFilter()  
  71.         {  
  72.             public   void  renderMarkup(MarkupWriter writer, MarkupRenderer renderer)  
  73.             {  
  74.                 Heartbeat heartbeat = new  HeartbeatImpl();  
  75.                 heartbeat.begin();  
  76.                 environment.push(Heartbeat.class , heartbeat);  
  77.                 renderer.renderMarkup(writer);  
  78.                 environment.pop(Heartbeat.class );  
  79.                 heartbeat.end();  
  80.             }  
  81.         };  
  82.         MarkupRendererFilter defaultValidationDecorator = new  MarkupRendererFilter()  
  83.         {  
  84.             public   void  renderMarkup(MarkupWriter writer, MarkupRenderer renderer)  
  85.             {  
  86.                 ValidationDecorator decorator = new  DefaultValidationDecorator(environment, spacerImage, writer);  
  87.                 environment.push(ValidationDecorator.class , decorator);  
  88.                 renderer.renderMarkup(writer);  
  89.                 environment.pop(ValidationDecorator.class );  
  90.             }  
  91.         };  
  92.         configuration.add("DocumentLinker" , documentLinker,  "before:RenderSupport" );  
  93.         configuration.add("RenderSupport" , renderSupport);  
  94.         configuration.add("InjectDefaultStyleheet" , injectDefaultStylesheet,  "after:RenderSupport" );  
  95.         configuration.add("ClientBehaviorSupport" , clientBehaviorSupport,  "after:RenderSupport" );  
  96.         configuration.add("Heartbeat" , heartbeat,  "after:RenderSupport" );  
  97.         configuration.add("DefaultValidationDecorator" , defaultValidationDecorator,  "after:Heartbeat" );  
  98.     }  

 

可以看出,这段代码为MarkupRenderer配置了六个过滤器,分别是文档链接过滤器(DocumentLinker)、渲染支持过滤器 (RenderSupport)、默认风格过滤器(InjectDefaultStyleheet)、客户端行为支持过滤器 (ClientBehaviorSupport)、心跳过滤器(Heartbeat)、默认验证修饰过滤器 (DefaultValidationDecorator)。组件在渲染过程中使用@Environment注入的的环境服务,都是在这个在这个阶段完成 的。所以,在渲染的时候,Environment就可以理解为渲染环境,这样就可以很容易的和Inject区分开了。

文档链接过滤器的主要职责是为环境加入一个管理页面资源链接(比如css和js)的环境服务 。如果某个组件需要引入资源,则可以通过这个环境服务加入,但Tapestry提供了一个更容易理解的方式,就算使 用渲染支持服务。

渲染支持过滤器的主要职责是为环境加入一个支持渲染的环境服务。DocumentLinker这个服务实际上就被包装在RenderSupport 服务中。RenderSupport服务还会提供分配id,添加初始化script代码等功能。

默认风格过滤器的主要职责是给RenderSupport中添加默认风格的css。

客户端行为支持过滤器的主要职责是为环境加入一个支持客户端行为的环境服务,比如关联zone,加入客户端验证等与客户端行为相关的支持。

心跳过滤器的主要职责是为环境加入一个心跳服务,并产生一次心跳。以保证渲染过程中最终会有一次心跳产生。

默认验证修饰过滤器的主要职责是为环境加入一个默认的验证修饰服务,也就是加入组件的客户端验证的一些修饰,比如输入框后的那个“红叉”图片。

Ajax请求的渲染过程也有这样一个管道,基本结构和这个管道的组织是一样的,除了少了一个默认风格过滤器。因为页面渲染时已经引入过来了,所以此 时不需要再引入资源了。

渲染环境的构造大概就是这样,这个管道的终端服务将会调用一个渲染队列,见下一节分解。

4.2调度策略

上一节提到了一个渲染队列org.apache.tapestry5.internal.services.PageRenderQueue。这个 队列是Tapestry调度页面渲染的核心,它负责激发组件的不同生命周期,调度整棵组件树依次执行,生成整个HTML页面。

在介绍具体的调度策略之前,我先介绍一些Tapestry组织组件的结构,这样才能更好的理解调度策略。

Tapestry组件在运行时有两个主要结构,一个是org.apache.tapestry5.ComponentResources,一个是 org.apache.tapestry5.internal.structure.ComponentPageElement。

ComponentResources是用于管理组件资源的结构,通常可以看作组件实例。它维护的是组件的资源,以及组件容器以及它的子组件的关 系。比如页面组件是谁,直接使用它的组件是谁,但它并不包含组件在模板中的关系,也就是模板中组件之间的嵌套关系。

ComponentPageElement则是用于维护组件在模板中的嵌套关系的结构,主要在渲染过程中使用。

我们用下面的一个页面说明这两个结构的区别。

Example.tml





  1. <
    html
     
    xmlns:t
    =
    "http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
    >
      

  2. <
    body
    >
      

  3.     <
    div
     
    t:type
    =
    "div"
    >
      

  4.         <
    span
     
    t:type
    =
    "WriteValue"
     
    value
    =
    "value"
    />
      

  5.     </
    div
    >
      

  6.     <
    form
     
    t:type
    =
    "form"
    >
      

  7.         <
    input
     
    t:type
    =
    "textfield"
     
    value
    =
    "value"
    />
      

  8.         <
    input
     
    t:type
    =
    "submit"
     
    value
    =
    "submit"
    />
      

  9.     </
    form
    >
      

  10. </
    body
    >
      

  11. </
    html
    >
      




Example.java

  1. public   class  Example {  
  2.     @Property   
  3.     @Persist   
  4.     private  String value;  
  5. }  

 

为了说明问题,我特地写了两个组件,一个是div(输出一个div标签),一个是WriteValue(输出参数value的值)。它们的代码分别 如下:

Div.java (该组件没有模板)

  1. public   class  Div {  
  2.     void  beginRender(MarkupWriter writer) {  
  3.         writer.element("div" );  
  4.     }  
  5.     void  afterRender(MarkupWriter writer) {  
  6.         writer.end();   
  7.     }  
  8. }  

 

WriteValue.tml

  1. < span   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" >   
  2.     ${value}  
  3. </ span >   

 

WriteValue.java

  1. public   class  WriteValue {  
  2.     @Property   
  3.     @Parameter (defaultPrefix=BindingConstants.PROP)  
  4.     private  String value;  
  5. }  

 

WriteValue和Div组件是两个非常简单的组件,它们并没有实际存在的价值,目的只是为了说明组件的渲染过程。

Example页面对应的ComponentPageElement组织组件的架构如下图所示。

 

图中绿色代表一个页面,也就是我们例子中的Example页面。黑色的标识一个HTML的标签,这些标签并不是Tapestry的组件,但是框架会 将它们按照标签处理,为什么这么处理我们在后续的中间结构说明。蓝色表示组件,页面中总共用到了Div、Form、WriteValue、 TextField和Submit组件。黄色表示与组件关联的模板或者内嵌元素(body)。红色表示表达式,用于输出value的表达式。

ComponentResources又是如何组织的呢?

这里就不给出图形说明了,因为它的结构更简单直接。Div、Form、WriteValue、TextField和Submit组件的父节点都是 Example页面。

之所以要用两个结构是因为,一个是用于显示的结构,一个是用于操作的结构。想想如果没有树形的结构,就很难输出与模板对应的HTML页面。而当操作 的时候,需要的是数据绑定和事件通知。而此时,我们只需要知道一个页面使用了那些组件,并不关心它们之间的排列结构。

理解了ComponentPageElement的组件树,再回想一下组件的生命周期,就能基本上了解渲染的调度过程了。

对于非组件的结构,比如Start[HTML],Expression等直接输出就可以了,而对于组件,则递归的调度其执行生命周期。这样,最后就 能完整的输出整个页面了。

整个渲染的流程大致如下:

Example页面首先被激活,由于没有定义生命周期响应函数,直接进入模板渲染阶段。此时,Start[HTML]和Start[Body]这连 个开始Tag被执行。之后,进入Div组件,由于Div组件具有BeginRender函数,所以进入该函数输出Div的开始Tag。接着因为Div组件 没有模板进入渲染body阶段,此时开始渲染WriteValue。WriteValue组件也因没有生命周期响应函数,所以直接进入模板。然 后,Start[Span]被执行输出span Tag。接着,Expression又被执行,输出参数value中的值,之后spnd的结束Tag输出。其后,由于WriteValue没有生命周期函 数,所以会直接回到Div的AfterRender阶段,输出div的结束Tag。随后便会调度Form、End[Body]和End[HTML]执行, 过程与前面类似,这里就不鳌述了。

但是为了提高渲染的效率,Tapestry并没有采用递归的方式调度,而是采用了一种非递归的方式调度页面渲染,并采用了一些消去策略,去除了一些 不存在的渲染阶段。比方说,如果一个组件并没有定义BeginRender事件,那么就没有必要激发这个事件。

为了实现这种非递归的算法,定义了一个叫做org.apache.tapestry5.runtime.RenderCommand的接口,和一些 列实现类,其中有些用于输出文本和HTML标签的,有一些用于输出Template或者Body,有一些用于输出表达式,而组件的每一个阶段都会有一个相 应的实现类。

PageRenderQueue虽然命名是一个队列,实际上是按照堆栈的方式调度RenderCommand执行。每个RenderCommand 都可以按照需要,在执行的过程中向PageRenderQueue中添加后继的RenderCommand。这样,就可以将递归的算法,转变为非递归的算 法。

具体算法涉及的面比较广,后续我给出一个这个例子的调用栈的实例,而具体算法就不给出来了。

这里我只说明一点比较特殊的地方,也就是template和body的渲染。

前面已经说过,template和body是非常相似的两个部分,不同的呢只是一个是定义在组件的模板中,一个是定义在组件的body中。所以,对 它们的处理也是很相似的。

不论组件是否定义了template,渲染template阶段都是存在的。如果组件定义了模板,则渲染的就是模板中的内容,如果组件没有定义模 板,那么渲染的就是body的内容。

对于body而言,如果组件有body,那么这个阶段就会实际的执行,如果没有,那么就会直接跳过。

也就是说,对于一个组件,template和body是2选1的,定义了模板,就不会自动的进入body,需要body就不能定义模板。

那么,如果即希望有模板,又希望有body怎么办呢?

Tapestry给出的解决方案是在模板中显示的给出一个<t:body/>标签,用来告诉组件,在模板的什么位置插入body的内 容。也就是说此时渲染body的行为类似于使用了一个组件<t:body/>完成,而不是框架自动的调度。

实际上,也只有这样template和body的语言才是明确的,不会因为框架的自动引入调用而打乱的显示内容。

下面,我们就仔细看看这个例子的实际调用过程,如果感兴趣可以点击展开(点击“+”),查看栈的变化,我已在栈中加入了注释。栈顶的元素是当前正处 理的RenderCommand,下一个栈中新增的元素则是这个RenderCommand向PageRenderQueue中新增的 RenderCommand。这个栈被调度了48次,从00开始,标注在最左边。

 

4.3中间结构

下面进入渲染的最后一个部分——中间结构。

加入这部分并不是应为它有什么特别,而是Tapestry5在这方面做了一次大的调整。页面在渲染过程中,并没有实际的输出到response的输 出流中,而是保存在一个中间结构中。

Tapestry采用了一种类似与XML文档结构的方法组织这个中间结构,也就是说和HTML文档DOM结构很相似的结构。

采用了这个结构的一个好处是页面在渲染过程中可以随意的在渲染的最后阶段修改和添加某些元素。比如Label组件,关联到了TextField组 件。但是一般Label会在TextField组件之前渲染,所以Label组件得不到TextField组件的ID。但是引入了DOM的中间结构,可以 在TextField组件渲染之后再为Label组件加入TextField的ID。

前面所谈到的MarkupWriter,正是维护这个DOM结构的输出器,调用它输出的内容会实际的输出到DOM中。

在页面渲染结束之后,DOM保存的数据就会实际的输出到response的输出流中。

这时我们在回到前面遗留的一个问题。为什么非组件的HTML标签也被作为DOM的元素处理呢?

因为这样不仅可以使页面严格的符号XHTML的标准,而且可以更好的处理js和css等资源的引进。也就是说如果页面自己有head标签,就可以在 这个标签中添加内容,如果页面没有head那么就可以在合适的位置插入head,再添加所需要的资源。

本实例输出的HTML代码如下所示,感兴趣可以展开(点击“+”)查看。仔细观察这个结果,会发现Form中有一个input存放了一段经过编码的 数据,该数据的作用我会在《解读Tapestry5.1——Form 》中详细说明。

  1. < html >   
  2. < head >   
  3.     < link   type = "text/css"   rel = "stylesheet"   href = "/assets/tapestry/5.1.0.5/default.css"   mce_href = "assets/tapestry/5.1.0.5/default.css" > </ link >   
  4.     < link   type = "text/css"   rel = "stylesheet"   href = "/assets/blackbird/5.1.0.5/blackbird.css"   mce_href = "assets/blackbird/5.1.0.5/blackbird.css" > </ link >   
  5.     < meta   content = "Apache Tapestry Framework (version 5.1.0.5)"   name = "generator" > </ meta >   
  6.     < mce:script   src = "/assets/scriptaculous/5.1.0.5/prototype.js"   mce_src = "assets/scriptaculous/5.1.0.5/prototype.js"   type = "text/javascript" > </ mce:script >   
  7.     < mce:script   src = "/assets/scriptaculous/5.1.0.5/scriptaculous.js"   mce_src = "assets/scriptaculous/5.1.0.5/scriptaculous.js"   type = "text/javascript" > </ mce:script >   
  8.     < mce:script   src = "/assets/scriptaculous/5.1.0.5/effects.js"   mce_src = "assets/scriptaculous/5.1.0.5/effects.js"   type = "text/javascript" > </ mce:script >   
  9.     < mce:script   src = "/assets/tapestry/5.1.0.5/tapestry.js"   mce_src = "assets/tapestry/5.1.0.5/tapestry.js"   type = "text/javascript" > </ mce:script >   
  10.     < mce:script   src = "/assets/blackbird/5.1.0.5/blackbird.js"   mce_src = "assets/blackbird/5.1.0.5/blackbird.js"   type = "text/javascript" > </ mce:script >   
  11.     < mce:script   src = "/assets/tapestry/5.1.0.5/tapestry-messages.js"   mce_src = "assets/tapestry/5.1.0.5/tapestry-messages.js"   type = "text/javascript" > </ mce:script >   
  12. </ head >   
  13. < body >   
  14.     < div >   
  15.         < span >   
  16.       
  17.         </ span >   
  18.     </ div >   
  19.     < form   onsubmit = "javascript:Tapestry.waitForPage(event);"   action = "example.form"   method = "post"   id = "form"   name = "form" >   
  20.         < div   class = "t-invisible" >   
  21.             < input   value = "H4sIAAAAAAAAAFvzloG1XJhB0LUiMbcgJ9WqJLWiJC0zNSeluIjBNL8oXS+xIDE5I1WvJLEgtbikqNJULzm/KDUnM0kvKbE4Vc8xCSiYmFziBtKiEpxaUlqgGnqY+6Ho8T9MDIw+DNzJ+XklRfk5fom5qSUMQj5ZiWWJ+jmJeen6wSVFmXnp1hUFJQyccEtxucSRVJcEFOUnpxYXB5cm5WYWF2fm5x1el2KS9m3eOSYGhoqCcgEGPpg1xSAlJUA7HPDakZyfW5Cfl5pXUqwHNrQE04pnD4O+NrsufgHxeGpOai5QOcjjhQx1DIwgj7JBLAMAHFY4RHQBAAA="   name = "t:formdata"   type = "hidden" > </ input >   
  22.         </ div >   
  23.         < input   id = "textfield"   name = "textfield"   type = "text" > </ input > < img   id = "textfield-icon"   class = "t-error-icon t-invisible"   alt = ""   src = "/assets/tapestry/5.1.0.5/spacer.gif"   mce_src = "assets/tapestry/5.1.0.5/spacer.gif" />   
  24.         < input   value = "submit"   name = "submit"   type = "submit" > </ input >   
  25.     </ form >   
  26. < mce:script   type = "text/javascript" > <!--  
  27. Tapestry.DEBUG_ENABLED  =  true ;  
  28. Tapestry.onDOMLoaded(function() {  
  29. $('textfield').activate();  
  30. });  
  31. // --> </ mce:script > </ body >   
  32. </ html >   

 

本文就到这里结束了,希望能讲述清楚了页面的渲染过程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值