前言
由于internet技术的广泛流行,越来越多的程序开发模式正在从c/s向b/s转换,由于b/s模式具有瘦客户端的优点――无须安装客户端程序,这就大大减轻了软件升级的费用,所有的业务逻辑和数据库存储都放在服务端,从而提高了系统的可维护性。而且由于http协议是一个开放式的标准,因此只要是支持http协议的浏览器都可以运行,再结合目前的xml技术,可以方便地实现跨平台的分布式应用。
但b/s架构有其天生的缺点:无状态性。这其实并不是b/s程序的错,而是由于b/s程序是建立在http协议的基础上的,因此程序无法维护各个客户端的状态,不过所幸的是这个问题现在已经解决了。
现行的b/s开发的另一大缺陷是:代码和html页面揉和在一起了,对程序员和美工非常不利,而且对代码的维护简直是一场恶梦,这相信是许多web开发者的一种体会。而c/s结构就没有这种缺陷,因为它是基于组件的,比如vb,你有多得数不清的activex控件来完成一些复杂的界面。
也许你看到这里,心里在盘算着:还是用c/s算了,省得这么多麻烦,但是一个不敢于尝试新技术的公司往往是一个被新技术淘汰的公司。
为了解决上述问题,apache组织开发了一个基于jsp的mvc模式的实现:struts。它将web表现层分为model-view-controller几个部分,通过一个servlet来对web的流程进行控件,这与单纯的jsp或asp相比确实进步了不少,因为利用struts能清楚地界定web的流程,而且不提倡使用<% …. %>语句,转而代之的是taglib技术。但它始终没有摆脱代码和html页面揉和的问题,而且它的原理对于jsp初学者来说比较复杂,taglib库不够丰富,而且taglib也不像组件那样能够继承。
于是,新一代的web开发方法应运而生,它们最典型的就是开发方法是基于组件的,其中最具代表性的就是tapestry。
为什么要用tapestry?
tapestry现在已经属于apache的jakarta项目下了,你可以访问http://jakarta.apache.org/tapestry 来访问更多的信息。
tapestry是一个强有力、开放源码、基于java的用于开发高端web应用的framework。它是jsp的一种替代方法,使用tapestry,可以让你的页面看不到一句jsp代码,这使页面看起来非常干净。它可以用很少的代码来构造一个极端复杂的web应用。
tapestry使用了类似于传统c/s的开发方法:基于组件的开发。使用tapestry,你就可以得到以下好处:
1、非常高的代码复用性,因为在tapestry中,任何事物都可以看作一个可复用的组件。
2、将jsp开发者从繁琐的jsp代码中解脱出来,取而代之的是真正面像对像方法,而不是url解析。
3、对页面国际化的充分支持
4、精确地错误报告,可以将错误定位到源程序中的行,取代了jsp中那些莫名奇妙地错误提示。
充分支持团队开发,美工人员和java开发人员可以融洽地相处,互相都不依赖于对方。
你也许会问:如果tapestry是基于组件的,那么它的组件是怎样构成的呢?它是由一个定义文件(以xml的格式)、一个html模板、一个java类。tapestry的组件可以组合在一起形成一个更大的组件或逻辑页面。
tapestry的工作原理如下:在web.xml文件中定义一个名为applicationservlet的servlet来进行处理所有的http请求,这和struts有点相似,不过它主要职责是负责引导整个tapestry核心,启动日志功能,读取配置文件,创建工作引擎来指派客户请求。applicationservlet主要是通过它的配置文件来进行自身管理的,你可以通过在web.xml中定义一个名为org.apache.tapestry.specification-path的初始参数来指定这个配置文件,当然如果你觉得这样麻烦,那你也可以不指定配置文件,但tapestry会自动寻找与此servlet同名的配置文件,例如在web.xml中的定义如下:
| <servlet> <servlet-name>registration</servlet-name> <servlet-class>org.apache.tapestry.applicationservlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> |
那么tapestry会自动寻找一个名为registration.application的配置文件,如果用户没有定义此文件,那么tapestry将不能正常启动。
为了便于理解,我举一个大家都熟知的例子:用户注册模块。首先生成一个注册页面,等待用户的输入,然后进行合法性检查,如果通过,就显示下一个页面。如下所示
也许你会说,咦,这不是和jsp的表单差不多的吗?是的,虽然看上去差不多,但原理大不一样,由于它是基于组件的,所以name、dob、address、city、state、zip和register按钮都是单独的组件,而不是普通的表单了。
这时你肯定会说:唉,也不过如此嘛,就算是基于组件的思想设计出来的东西也不过尔尔,这几个文本框这么简单,何必费这么大的劲把它们包装成组件呢?呵呵,不知你注意到dob这个文本框没有?它和其它几个有些不一样,它的右边多了一个小按钮,如果点击此按钮,出现的结果会让你瞠目结舌!
酷吧?如果你曾经是一个jsp程序员,你的脑海里马上会闪过这样一个念头:一定是用javascript做的! 如果你这样想的话,那你就大错特错了,因为本人的javascript还没达到这种水平,如果我告诉你完成这样一个组件,只需要在配置文件中加入通俗易懂的几个xml元素的时候,你是否会觉得这真的很神奇呢?
是的,这就是tapestry的魅力所在,tapestry主要设计者howard lewis ship的目的就是最大限度地解放jsp程序员的劳动量,让他们有更多的时间花在客户的需求和程序的逻辑设计上,而不是花在和美工人员互相解决矛盾冲突上,你可能会急不可待的问:那我们是否可以动手试一试了呢?是的,当然可以,但在动手之前,我要先介绍一些关于tapestry的概念,并试着开发一个最简单的“hello,world”程序来让你熟悉一下它的配置。因为它是一个framework,如果你对它的结构不是很清楚的话,我想你肯定无法充分地利用它的。
一个tapestry组件一般称作jwc(java web component),它能与其它jwc一起组合,生成实例,进行配置。比如你刚才看到的那个不可思议的组件就是一个datepicker组件,刚才的那个页面是一个page组件,tapestry自带了很多的jwc组件,这些组件不仅仅只是gui组件,还有可能是带控制功能的组件,比如foreach组件能够提供循环的功能,同时tapestry还提供了一个简单的方法来制作用户自定义的组件,所有的tapestry应用的表示层都是由这些jwc构成的。
一个简单的hello,world程序
为了清楚地弄清tapestry的framwork构架,我们从最简单的”hello,world”开如,打开组件的暗箱来看一看jwc的内部结构。一个典型的jwc包括3个部分----一个html模板,一个xml格式的定义文件和一个或多个java类,尽管一个简单的jwc可以仅仅是一个html模板,但在本例中我们还是使用这三个部分来展示它的内部结构。
先让我们看一下演示的结果:

由于我们知道page也是一种jwc组件,因此让我们看一下这个page组件是怎样构成的,首先,我们看一下它的html模板结构:
home.html
| <html> <head> <title>welcome to tapestry!</title> </head> <body> hello <span jwcid="user">user name</span>! welcome to tapestry! </body> </html> |
这里你可能看上去觉得非常眼熟,这不就是一个普通的html网页吗?且慢,如果你仔细观察,会发现span标签多了一个新的属性jwcid,这正是tapestry设计的精妙之处,它的表现层可以说几乎是99%的纯html,只是增加了一个jwcid属性,jwcid=”user”在这里的意思是在<span> … </span>之间放置一个名为user的组件。这里要指出的是jwcid属性不仅仅可以放在span标签内,它可以放在任何的html标签内,比如<title>、<body>、<form>等等,至于为什么要选<span>的原因是<span>标签不影响网页的输出效果,这就大大方便了美工人员,他们可以直接使用frontpage或dreamwaver来进行设计,而不像jsp那样无法在frontpage或dreamwaver中正常显示。
也许你会问,网页上输出的用户名明明是jack嘛,为什么html模板中是user name呢?这是因为当tapestry一旦发现某个html标签有jwcid的属性后,它就会把这个标签当做一个组件来看待,至于标签里面的东西到底是原样输出或忽略就要依照那个组件的类型而定了,在这里我们的user组件实际上是一种insert类型的组件,因此会把<span>标签内的内容忽略掉,其实也可以直接写成<span jwcid=”user”></span>,效果也是一样的,只不过加了user name后就方便了美工人员的排版设计。
请注意,html模板有它自己的命名规范,它的文件名应该与page组件的定义文件相同,只不过是扩展名为.html,与普通的web服务器一般都有一个index.html类似,tapestry也有自己的”index.html”,只不过它的名字不是”index.html”,而是home.html,那么这些模板文件究竟应该放在哪儿呢?很简单,放在当前应用的目录下面就行了,比如我们的应用是welcome,那把它放在webapps/welcome目录下面就行了。
home.page
| <?xml version="1.0" encoding="utf-8"?> <!doctype page-specification public "-//apache software foundation//tapestry specification 3.0//en" "http://jakarta.apache.org/tapestry/dtd/tapestry_3_0.dtd"> <page-specification class="demo.home"> <component id="user" type="insert"> <binding name="value" expression="username"/> </component> <context-asset name="$template" path="home.html"/> </page-specification> |
以上这个文件便是这个名为”home”的page组件的页面定义文件,它好像是一座桥,联系着html模板和java类进行协同工作。这个定义文件“home.page”实际上就是一个xml文件,它的根元素是page-specification,它有一个名为class的属性,用来指示html模板与哪个java类协同工作,在本例中为demo.home这个类。
component元素是对html模板中引用的组件的定义,id属性名一定要与html模板中的jwcid一样,type指定了这个组件属于哪种类型,在本例中为insert组件,insert组件是tapestry中的一个基本组件,它的作用是在html模板中插入指定的文本,待插入的文本的值由insert的value属性来决定,在本例中,value属性的表达式值是username。你这时候可能会觉得越看越糊涂:那为什么最后运行结果会是jack呢?不要着急,关于这个我们马上就要详细地进行讲解。我们还是先看一下最后一个元素context-asset,这个元素是定义这个page组件要用到的一些附加资源,比如图像、css格式文件等等之类的东西
现在我们开始认真地讲一下<binding name="value" expression="username"/>这个元素的含义,由于html模板嵌入的是一个insert类型的组件,而insert组件的功能是插入一段文本到html模板中去,对于本例而言,或许可以直接地指定要插入文本的内容为“jack”,但对一个实际的应用来说,往往是要从数据库从取出用户的名称,然后将该名称插入到html模板中去,那么就不能直接指定文本的内容了,怎么办呢?我们马上想到的办法是:指定一个java类,通过javabean的属性方法来获得数据库中的用户名,就像jsp中的<jsp getproperty … />标签一样,不错,这的确是一个好方法,tapestry正是这样做的,因此<binding name="value" expression="username"/>这个元素中的expression的值正是demo.home这个java类的一个属性,也就是说将demo.home这个类的username属性绑定到insert组件的value属性中去,而我们的创始人howard lewis ship先生并没有重头对这个绑定的功能进行设计,因为在软件开发中有一句著名的话:”不要重复地发明同一个轮子“,这个绑定功能是由另一个开放源码的工具完成地---- object graph navigation library,简称ognl(关于ognl具体可以参考http://www.ognl.org )。其内部真正的转换如下图所示:
其中,insert这个jwc组件有一个名为value的属性,而我们在home.page中将它与一个外部对像username进行绑定,在程序真正运行的时候,html模板中的user组件读取demo.home这个java类的username属性,然后通过ognl工具对insert组件的value属性进行更新,最后user组件将更新后的值插入到模板当中。好了,这时候你应该明白了insert组件是怎样取值的吧。
需要强调的是page组件是一种特殊的jwc组件,它可以包含其它的jwc组件,但不能被其它的jwc组件所包含。除此之外,page组件还有一些特殊的属性和功能,它也不能和其它组件进行组合而生成新的组件。
组件的命名方式和html模板的命名方式有些不同,它可以以.page作为扩展名(如果是page组件),或者以.jwc作为扩展名(如果是非page组件)。组件一般放在当前web应用的web-inf目录下面,以本例来说,我们的home.page这个文件就应该放在webapps/welcom/web-inf目录下面。
home.java
| package demo; import org.apache.tapestry.html.basepage; public class home extends basepage { private string username = "jack"; public string getusername() { return this.username; } } |
最后一部分便是我们的demo.home这个java类,它实际上就是一个普通的javabean,唯一不同的就是它必须从basepage类中继承。它的作用就是为html模板中的user组件提供文本信息的来源,在本例中我们简单的返回”jack”,但实际上getusername方法还可以从jndi、数据库、ejb中取得用户名。
其实demo.home不仅仅只是提供数据来源,它还可以实现表单组件的提交。tapestry提供了2个基本类方便用户进行扩充,一个是basepage类(专用于page组件),另一个是basecomponent类(用于用户自定义组件)。通过继承这些组件,可以大大地减轻用户的编程量,从而把精力放到程序流程设计上去。
demo.home组件类的名称应该与home.page中定义的名称一致,它一般放在当前web应用的web-inf/classes目录下,以本例来说,home.class这个类应该放在webapps/welcome/web-inf/classes/demo目录下面。
welcome.application
| <?xml version="1.0" encoding="utf-8"?> <!doctype application public "-//apache software foundation//tapestry specification 3.0//en" "http://jakarta.apache.org/tapestry/dtd/tapestry_3_0.dtd"> <application name="tapestry illustration 1"> <page name="home" specification-path="home.page"/> </application> |
以上这个文件是这个tapestry应用的配置文件,它是以application作为根元素的,其中page元素指定了一个名为home的page组件,它的文件名为home.page,当然你也可以指定更多的page组件,这个配置文件就像是一个总装车间,把一个个page组件或其它的组件装配起来。
关于这个配置文件的命名规范我们在前面已经讲过了,它一般放在web-inf目录下,在本例中它放在了webapps/welcome/web-inf目录下面。
web.xml
| <?xml version="1.0"?> <!doctype web-app public "-//sun microsystems, inc.//dtd web application 2.2//en" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"> <web-app> <display-name>tapestry welcome application</display-name> <servlet> <servlet-name>welcome</servlet-name> <servlet-class>org.apache.tapestry.applicationservlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>welcome</servlet-name> <url-pattern>/app</url-pattern> </servlet-mapping> <session-config> <session-timeout>15</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app> |
listing 5. web.xml
这个文件相信所有的jsp/servlet开发员都非常熟悉了吧?在这个文件中我们定义了一个名为welcome的servlet,它实际上就是org.apache.tapestry.applicationservlet这个类,它的主要作用是对客户的请求进行包装,然后转发到各个page组件进行处理,当然如果你有特殊需要,也可以继承applicationservlet类(当然这种情况很少出现)。
其中<servlet-mapping>用来进行地址映射,相信servlet开发员应该都知道它的作用吧?我们把所有对于/app的请求全部转发给welcome这个servlet进行处理,当然你也可以将/app换成其你自己的定义。最后我们打开一个ie浏览器,在地址栏中输入http://localhost:8080/welcome/app 就会看到结果。
好了,现在我们就讲一下关于上面那个用户注册的例子吧。由于篇幅的关系,我就不把所有的配置文件一一罗列了,只摘录一些关键的配置。
custinfo.html
| <html jwcid="@shell" title="welcome page"> <body jwcid="@body"> <form jwcid="@form" listener="ognl:listeners.submit"> customer name: <input jwcid="custname" type="text"/> date-of-birth: <input jwcid="dob" type="text" format="mmm dd, yyyy"/> (month dd, yyyy) <input type="submit" value="submit"/> </form> </body> </html> |
也许你看了上面这个文件不禁会问:为什么有的组件名称前面加了一个“@”?原因如下:tapestry由于是由众多组件组成的,其中一般的组件都要在其page组件中用<component>元素进行定义,对于一些简单的或者无其它附加参数的组件来说就显得比较麻烦,因此tapestry提出了显式定义组件和隐式定义组件这个概念,显式定义是指明确地在page组件中定义过的组件,隐式定义是指没有明确地在page组件中定义的组件,都要在组件名字前加一个“@”。
这里有一个组件显得很特别:@form组件,这个组件由于是隐式定义地,因此它的参数就直接在html模板中进行定义:listener="ognl:listeners.submit",它表示当用户按下submit按钮后页面流程会交给当前page组件类(即welcome这个类)的submit函数进行管理。
下面我们再看一下它的page组件的定义:
custinfo.page
| <?xml version="1.0" encoding="utf-8"?> <!doctype page-specification public "-//apache software foundation//tapestry specification 3.0//en" "http://jakarta.apache.org/tapestry/dtd/tapestry_3_0.dtd"> <page-specification class="demo.welcome"> <property-specification name="custname" type="java.lang.string"/> <property-specification name="dob" type="java.util.date"/> <component id="custname" type="textfield"> <binding name="value" expression="custname"/> </component> <component id="dob" type="datepicker"> <binding name="value" expression="dob"/> </component> </page-specification> |
其中值得关注的是dob这个组件,它的类型是tapestry核心组件库中的datepicker组件,有了它,我们就可以生成先前那个不可思义的选择日期的界面了。
这里出现了一个新面孔:property-specification元素,它是干什么用的呢?还是先让我们看一下那个“hello,world”例子中的组件类的定义吧:
home.java
| package demo; import org.apache.tapestry.html.basepage; public class home extends basepage { private string username = "jack"; public string getusername() { return this.username; } } |
其中username这个属性是一个javabean属性,通过get或set方法来存取username的值,当然对于本例,property-specification这个元素和它一样,就是定义了一个javabean的属性,其实你也可以像上例那样进行存取,而不需在custinfo.page中进行定义。定义property-specification元素的根本原因其实很可笑:为了偷一点懒! 为什么这么说呢?因为如果你用property-specification元素来定义javabean的属性的话,那你在组件类中就不必实现其get或set方法了,只需用一个抽像方法来完成,至于真正的get或set方法的实现就由tapestry来代劳了,其中,welcome这个类的代码如下:
| welcome.java package demo; import java.util.date; import org.apache.tapestry.irequestcycle; import org.apache.tapestry.html.basepage; public abstract class welcome extends basepage { public abstract void setcustname(string custname); public abstract void setdob(date dob); public abstract string getcustname(); public abstract date getdob(); public void submit(irequestcycle cycle) { if (getcustname() != null && !getcustname().trim().equals("") && getdob() != null) { welcome welcome = (welcome) cycle.getpage("welcome"); welcome.setcustname(getcustname()); welcome.setdob(getdob()); cycle.activate(welcome); } } } |
前面的几个抽像方法就不多说了,welcome类的submit方法和在html模板中定义的@form组件中的listener参数正好对应,也就是说,当用户按下提交按钮后,这个submit方法就会被激活,它进一步地引导着页面下一步的动作。在本例中它先检查用户名和用户出生日期(dob,date of birthday的简称)是否为空,如果不为空,就将用户在页面中输入的值赋予welocome这个类的custname和dob这两个属性,然后激活welcome这个page组件,也就是将页面跳转到welcome这个page上去。
需要注意的是,form组件定义的监听类方法必须为public,并且要带一个irequestcycle 的参数,irequestcycle是一个接口,它是由tapestry提供的一个对于用户而言,当前会话的一个管理工具。
总结
到目前为止,你大概明白了tapestry的原理了,但是一个复杂的tapestry应用还是要考虑到很多问题的,比如页面的定义,页面之间的流程,组件元素的持久性设计,与ejb或原有的jsp系统的集成等等问题,但是tapestry都已经为你考虑好了,你甚至可以把它的源码下载下来仔细研究,也可以到它的邮件列表上发表自己的观点,这正是我所喜爱的----open source ! 它代表着自由!
闽公网安备 35060202000074号