Java Web开发的轻便架构Tapestry5---页面渲染之框架职责

          在一篇博客我介绍了Tapestry5的页面渲染的部分内容,今天主要介绍一下页面渲染中的框架职责.

1.框架职责

          所有的底层服务,以及组件的调度都是由框架负责的。所以,要深入了解渲染的过程,仅了解组件的职责还是不够的。当一个请求实际到达渲染处理器 时,该处理器首先会向目标页面发出一个activate事件,为页面提供一个准备渲染,并处理传入参数的机会。之后,渲染流程就会传入一个org.apache.tapestry5.internal.services.PageResponseRenderer服务,该服务会实际的调度页面渲染。
          我们知道渲染过程大致如下:首先,获得页面类型(content type),默认为"text/html";然后,根据这一类型生产一个输出org.apache.tapestry5.MarkupWriter,组件若有需要输出HTML片段,则会输出到这个输出器中;其后org.apache.tapestry5.internal.services.PageMarkupRenderer这一渲染器将会开始调度组件开始执行其生命周期;最后,MarkupWriter生成的中间结构会实际的输出到返回给客户端的输出流中。
          我们知道activate是用于给页面一个初始化的机会,其它页面跳转过来时传递的参数就是通过这个事件让目标页面获取的。那么passivate呢?passivate是同一个页面渲染结束时,希望传入下一次渲染的参数。一个典型的用途是在Form提交事件处理完之后,传递参数给接下来的渲染使用。因为Tapestry默认的会在Form提交后,通过重定向渲染页面。也就是说渲染实际上是一个新的请求,Form提交事件里生成的数据,都被清空了。为了让渲染可以获得Form处理事件产生的数据,一个不用消耗存储空间(session)的方式就是在passivate事件中返回所需的数据。因为这个数据会保存在URL里面,在下一次请求到达服务器时传递到activate这个事件中。
          所以,为了保证passivate返回的数据能传到activate事件,就必须保证不管通过何种方式,链接也好,Form也好,重定向也好,传递到服务上的URL中都保存有passivate返回的数据。
          因此,为了保证数据的可回传性,passivate事件并不是在渲染的某个阶段被激发,而是在生产链接的服务里被激发。由于页面所有的链接都是通过这个服务生产,所以数据就肯定可以回传到activate事件。实际上,这个时候有一件事情就需要注意了,passivate的事件处理函数最好能缓存一下需要返回的数据,不要每次都构造。

2.环境构造

          环境构造是页面渲染器(PageMarkupRenderer)的一部分,对应于org.apache.tapestry5.services.MarkupRenderer这个服务。Tapestry使用了其一贯的风格,采用一个管道了组织这个服务,管道配置了六个过滤器,分别是文档链接过滤器(DocumentLinker)、渲染支持过滤器(RenderSupport)、默认风格过滤器(InjectDefaultStyleheet)、客户端行为支持过滤器(ClientBehaviorSupport)、心跳过滤器(Heartbeat)、默认验证修饰过滤器(DefaultValidationDecorator)。组件在渲染过程中使用@Environment注入的的环境服务,都是在这个在这个阶段完成的。所以,在渲染的时候,Environment就可以理解为渲染环境,这样就可以很容易的和Inject区分开了。
          文档链接过滤器的主要职责是为环境加入一个管理页面资源链接的环境服务。如果某个组件需要引入资源,则可以通过这个环境服务加入,但Tapestry提供了一个更容易理解的方式,就算使用渲染支持服务。
          渲染支持过滤器的主要职责是为环境加入一个支持渲染的环境服务。DocumentLinker这个服务实际上就被包装在RenderSupport服务中。RenderSupport服务还会提供分配id,添加初始化script代码等功能。
          默认风格过滤器的主要职责是给RenderSupport中添加默认风格的css。
          客户端行为支持过滤器的主要职责是为环境加入一个支持客户端行为的环境服务,加入客户端验证等与客户端行为相关的支持。
          心跳过滤器的主要职责是为环境加入一个心跳服务,并产生一次心跳。以保证渲染过程中最终会有一次心跳产生。
          默认验证修饰过滤器的主要职责是为环境加入一个默认的验证修饰服务,也就是加入组件的客户端验证的一些修饰,比如输入框后的那个“红叉”图片。
          Ajax请求的渲染过程也有这样一个管道,基本结构和这个管道的组织是一样的,除了少了一个默认风格过滤器。因为页面渲染时已经引入过来了,所以此时不需要再引入资源了。渲染环境的构造大概就是这样,这个管道的终端服务将会调用一个渲染队列

3调度策略

          在前面提到了一个渲染队列org.apache.tapestry5.internal.services.PageRenderQueue。这个队列是Tapestry调度页面渲染的核心,它负责激发组件的不同生命周期,调度整棵组件树依次执行,生成整个HTML页面。
          Tapestry组件在运行时有两个主要结构,一个是org.apache.tapestry5.ComponentResources,一个是org.apache.tapestry5.internal.structure.ComponentPageElement。
          ComponentResources是用于管理组件资源的结构,通常可以看作组件实例。它维护的是组件的资源,以及组件容器以及它的子组件的关系。比如页面组件是谁,直接使用它的组件是谁,但它并不包含组件在模板中的关系,也就是模板中组件之间的嵌套关系。
          ComponentPageElement则是用于维护组件在模板中的嵌套关系的结构,主要在渲染过程中使用。
          WriteValue和Div组件是两个非常简单的组件,它们并没有实际存在的价值,目的只是为了说明组件的渲染过程。
          Example页面对应的ComponentPageElement组织组件的架构如下图所示。
 

          图中绿色代表一个页面。黑色的标识一个HTML的标签,这些标签并不是Tapestry的组件,但是框架会将它们按照标签处理,为什么这么处理我们在后续的中间结构说明。蓝色表示组件,页面中总共用到了Div、Form、WriteValue、TextField和Submit组件。黄色表示与组件关联的模板或者内嵌元素(body)。红色表示表达式,用于输出value的表达式。
          Div、Form、WriteValue、TextField和Submit组件的父节点都是Example页面。之所以要用两个结构是因为,一个是用于显示的结构,一个是用于操作的结构。而当操作的时候,需要的是数据绑定和事件通知。
对于非组件的结构,比如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,渲染template阶段都是存在的。如果组件定义了模板,则渲染的就是模板中的内容,如果组件没有定义模板,那么渲染的就是body的内容。对于body而言,如果组件有body,那么这个阶段就会实际的执行,如果没有,那么就会直接跳过。也就是说,对于一个组件,template和body是2选1的,定义了模板,就不会自动的进入body,需要body就不能定义模板。Tapestry给出的解决方案是在模板中显示的给出一个<t:body/>标签,用来告诉组件,在模板的什么位置插入body的内容。也就是说此时渲染body的行为类似于使用了一个组件<t:body/>完成,而不是框架自动的调度。实际上,也只有这样template和body的语言才是明确的,不会因为框架的自动引入调用而打乱的显示内容。

4.中间结构

          页面在渲染过程中,并没有实际的输出到response的输出流中,而是保存在一个中间结构中。Tapestry采用了一种类似与XML文档结构的方法组织这个中间结构,也就是说和HTML文档DOM结构很相似的结构。采用了这个结构的一个好处是页面在渲染过程中可以随意的在渲染的最后阶段修改和添加某些元素。比如Label组件,关联到了TextField组件。但是一般Label会在TextField组件之前渲染,所以Label组件得不到TextField组件的ID。但是引入了DOM的中间结构,可以在TextField组件渲染之后再为Label组件加入TextField的ID。
          在页面渲染结束之后,DOM保存的数据就会实际的输出到response的输出流中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

弯_弯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值