3 实战
通过上面的演练,我们知道了如何定制自己Turbine应用中的Layout、Navigation和Screen。本节将带领大家一起从头实现。
3.1 Velocity
在上面的演练中,我们一句java代码都没编写,连Web Server都没有重启,仅仅是修改了一些“.vm”文件,就实现了页面布局的调整、显示内容的变化等工作,而这样一切神奇魔法的幕后功臣就是:Velocity。
为了在我们的Web Application中使用Velocity,需要在TurbineResources.properties文件中做如下配置:
…
# 开启Velocity Service
services.VelocityService.classname=org.apache.turbine.services.velocity.TurbineVelocityService
…
services.VelocityService.template.extension=vm
services.VelocityService.default.page = VelocityPage
services.VelocityService.default.screen=VelocityScreen
services.VelocityService.default.layout = VelocityECSLayout
services.VelocityService.default.navigation=VelocityNavigation
services.VelocityService.default.error.screen = VelocityErrorScreen
services.VelocityService.default.layout.template = Default.vm
…
services.VelocityService.file.resource.loader.path = /templates/app,/templates/flux
…
上图中只包含了几个比较重要的配置项,完整的配置模板在TDK例程的WEB-INF/conf/目录下的“TurbineResources.template”文件中。一般情况下,按照模板内的默认值进行配置就可以了,自己只需要修改以下两个选项:
l services.VelocityService.default.layout.template-默认Layout的模板名。
l services.VelocityService.file.resource.loader.path-模板文件的存放路径。以“,”间隔,根目录为Web Application所在目录。
我们先在下面的叙述中穿插讲述Velocity的使用方法,然后将另起篇幅和大家一起详细分析Velocity的执行机制。
3.2 Loader
在《Turbine简述》中提到,Turbine Servlet通过各种Loader动态装载Turbine中的五个模块:Action、Page、Screen、Navigation、Layout。Turbine中的所有Loader均派生自GenericLoader。Turbine 2.2中,GenericLoader表现为一个继承自Hashtable的纯虚类:
GenericLoader中,唯一的一个虚方法就是exec()。在这里,Turbine使用了Adapter Pattern和Command Pattern将Loader和各个Module结合在了一起,以LoginUser Action为例:
在这里,Turbine Servlet只管调用ActionLoader的exec()方法,不需要知道Action的接口信息,接口的转换在ActionLoader.exec()内完成。ActionLoader担任了Adapter Pattern中Adapter的职责,将源接口(Action.doPerform())转换成了目标接口(GenericLoader.exec())。使用Adapter Pattern,使得Turbine中各个模块的添加变得更为简单,如果日后需要的话,随时可以添加Action、Layout之外的任何模块,而对现有代码几乎没有影响。(不过值得提醒的是,Turbine目前只是定义了这个Adapter Pattern的框架,在实际的代码中并没有很好地利用)
同时,ActionLoader只管调用Action中的doPerform()方法,不需要知道LoginUser的任何信息,也不用管LoginUser是怎样接受请求,更不用管LoginUser事务处理的具体细节。Turbine通过这种方式实现了Command Pattern。这样,通过继承Action类,Turbine Application的开发者可以很轻松的扩展自己的Action,实现自己的专有事务;并且,通过这种模式,开发者很容易将已有的两个或多个Action组合为一个请求序列,以简化调用代码,使得项目代码更加清晰整洁、易于维护(当然,为了实现这一点,还需要用到Component Pattern,本文就不再针对此问题进行展开了)。
类似于ActionLoader,其他模块也按照上面的方法进行协同工作。通过使用这样的设计模式,Turbine Application就能很轻松的扩展,为再开发带来非常大的便利。再通过与TurbineResources.prooerties资源文件的结合,使得Turbine Application的可扩展性非常之强;并且,如果需要给已存在Application打补丁的话,也将是非常轻松便利。
3.3 Modules
Turbine中几大Module的具体实现都基本相同,因此,下面的篇幅将针对各模块的重点进行讲解,其他模块的相同部分将会一笔代过。
3.3.1 Screen
Screen是Turbine中最重要的表述层元素。最终页面中的大部分HTML代码将在此处生成。
3.3.1.1 HelloWorld Screen
依照惯例,我们先来看基于Velocity的HelloWorld:
package com.yourcompany.app.modules.screens;
// Velocity Stuff
import org.apache.velocity.context.Context;
// Turbine Stuff
import org.apache.turbine.util.RunData;
import org.apache.turbine.modules.screens.VelocityScreen;
public class HelloWorld extends VelocityScreen
{
public void doBuildTemplate( RunData data, Context context )
throws Exception
{
// the context object has already been setup for you!
context.put ("hello", "this is a test...");
}
}
HelloWorld Screen的包名应与TurbineResources.properties中的“module.packages”设置一致,对于此例来说,“module.packages”应做如下配置:
module.packages= com. yourcompany. app.modules
Note:包名可以有多个,各个包名之间使用“,”进行分隔。
Okay,就像我们所看到的,我们甚至不用返回任何对象,仅需要在Velocity Context中填充一些数据就可以了!
接下来,我们需要为我们的HelloWorld Screen创建一个名为HelloWorld.vm的模板:
<p>
<font color="red">
$hello of the emergency broadcast station.
</font>
</p>
你没看错,就这么简单!
还记得要将它放在哪里吗?没错,就是“TEMPLATES-PATH/screens/”下。
现在,通过下面这样的URL,就能访问我们的HelloWorld了:
http://www.server.com/servlet/TurbineServlet/template/HelloWorld.vm
3.3.1.2 魔法的后面
在我们看到的最终结果中,是一个完整(fully formed)的HTML页面,.vm文件中的$hello已经被“this is a test...”所取代。
Velocity究竟在幕后做了些什么呢?你也许会感到诧异。现在,就让我来为你揭开谜底:
l 首先,Turbine会查看是否存在HelloWorld这个java类,如果存在,就执行它[i]。这个java类负责给.vm文件中的$hello赋值。
l 然后,调用Velocity的模板引擎,来解释、执行HelloWorld.vm模板文件(例如,生成相应Layout、Navigation等)
l 最后,将执行结果返回给用户
可以看到,使用Velocity后,相比起Servlet,一个页面的生成已经变得非常简单,并且,完全可以由网页设计者(负责.vm文件的编写)和程序开发员(负责java类的编写)协同完成同一个项目而又不互相牵制。而且,在这个过程中生成的模板文件是非常容易管理和重用的[ii]。此时,如果对应到MVC模式的话:
l M – java类
l V – .vm文件(完全符合HTML格式)
l C – Turbine
[i] Turbine首先将.vm文件名的第一个字母大写,再去classpath中查找是否有相应的类。例如,如果模版文件名为Normal.vm或normal.vm的话,Turbine就会去查找是否存在类名为Normal的这个类;同样,helloWorld.vm对应HelloWorld这个类,role_editor对应Role_editor这个类。
[ii] 可以从示例看出,Screen的模板完全没有包含、混杂任何与Layout、Navigation相关的内容,这使得它很容易以“插件”的形式载入到任何地方。