java web framework综述
0.简介
本文介绍java web framework的基本工作原理,和一些常用的开源web mvc framework(struts, web work, tapestry, echo, jsf, maverick, spring mvc, turbine, cocoon, barracuda)。
web开发的最重要的基本功是http;java web开发的最重要的基本功是servlet specification。http和servlet specification对于web server和web framework的开发实现来说,是至关重要的协议规范。
应用和剖析开源web framework,既有助于深入掌握http & servlet specification, 也有助于了解一些现代的b/s web框架设计思想,如mvc,事件处理机制,页面组件,ioc,aop等。在这个现代化的大潮中,即使servlet规范本身也不能免俗,不断引入filter、listener等现代框架设计模式。同是sun公司出品的jsf更是如此。
关于mvc模型、项目简介、配置文件、入门示例等基础知识,网上已经有大量的重复资料信息,本文不再赘述。
文中会提到一些相关的开源项目,和一些编程思想,如有需要,可以用相关的关键字在网上搜索,获取基本的背景知识。
本文力图言简意赅,突出重点。着重描述其他资料没有提到、或很少提到的较重要内容,如运行原理、主流用法,相关知识,关键特性等。
1. java web程序工作原理
tomcat的server.xml文件中定义了网络请求路径到主机本地文件路径的映射。比如,<context path="/yourapp" docbase="yourapp_dir/webapp"/>
我们来看一下,一个http request-response cycle的处理过程。
http request url一般分为三段:host, context, path。
如http://yourhost/yourapp/en/index.html这个url,分为host=yourhost, context=yourapp, path=en/index.html三段。其中,context部分由request.getcontext()获得,path部分由request.getservletpath()获得(返回结果是“/en/index.html”)。
yourhost主机上运行的tomcat web server接收到这个url,根据context定义,把yourapp这个网络路径映射为yourapp_dir/webapp,并在此目录下定位en/index.html这个文件,返回到客户端。
如果我们这个url更换为http://yourhost/yourapp/en/index.jsp,这个时候tomcat会试图把yourapp_dir/webapp/en/index.jsp文件编译成servlet,并调用运行这个servlet。
我们再把这个url更换为http://yourhost/yourapp/en/index.do。
注意,戏剧化的事情就发生在这个时候,servlet规范中最重要的类requestdispatcher登场了。requestdispatcher根据web-inf/web.xml配置文件的定义,调用对应的servlet来处理en/index.do这个路径。
假设web.xml里面有这样的定义。
<servlet>
<servlet-name>dispatchservlet</servlet-name>
<servlet-class>yourapp.dispatchservlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatchservlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
那么,requestdispatcher会调用yourapp.dispatchservlet类处理这个路径。
如果web.xml没有定义对应en/index.do这个路径的servlet,那么tomcat返回“您请求的资源不存在”。
requestdispatcher用于web server中,也可以用于应用程序中进行处理转向,资源定位。比如,我们在处理en/index.do的代码中调用,
request.getrequestdispatcher(“cn/index.jsp”).forward(request, response), 就可以转交另外的资源cn/index.jsp来处理。
几乎所有的web framework都需要定义自己的dispatch作用的servlet,并调用requestdispatcher进行转向处理。
阅读web framework源代码,有两条主要线索,(1)根据web.xml找到对应的servlet类;(2)搜索包含“requestdispatcher”词的代码文件。
我们看到,request, response 这两个参数,被requestdispatcher在各种servlet之间传来传去(jsp也是servlet)。所以,request的setattribute()和getattribute()方法是servlet之间传送数据的主要方式。
在mvc结构中,一般的处理流程如下:
处理http request的基本单位一般称为action,是一个比servlet轻量得多的接口定义,通常只有一两个方法,如execute(perform), validate等。
我们知道,url->servlet映射,定义在web.xml配置文件里,但mvc框架通常会有另外一个定义url-> action映射的配置文件。
入口dispatcher servlet根据url -> action的映射关系,把请求转发给action。
action获得输入参数,调用商业逻辑,并把结果数据和view标识给(model & view)返回给dispatcher servlet。
dispatcher servlet根据这个view 标识,定位相应的view template path,把处理转交给view(jsp +taglib, velocity, free marker, xsl等)。
view一般通过request.getattribute()获得结果数据,并显示到客户端。至于是谁把结果数据设置到request.attribute里面,有两种可能:action或dispatcher servlet。
2. struts
http://struts.apache.org/
struts是目前用户群最大、开发厂商支持最多的开源web framework。
struts劳苦功高,为普及mvc框架作出了不可磨灭的贡献。显赫的声望,趋于老化的厚重结构,令struts成为很多现代web framework参照、挑战的目标。
struts应用主要包括3件事情: 配置struts-config.xml文件,实现action类,实现view;还有一些高级扩展用法。下面分别讲述。
1. 配置struts-config.xml文件:
struts支持多级配置文件,具体用法和限制,详见struts文档。这里只讨论struts-config.xml主流配置的内容。:-)
(1) url path到action的映射。
如<action path="/logonsubmit" type="app.logonaction" … />
struts的入口servlet是actionservlet。
actionservlet需要此信息把url path调用对应的action类处理。
在struts运行期间,一个url path,只存在一个对应的struts action实例。所有的该url path的请求,都经过这同一个struts action实例处理。所以struts action必须线程安全。
想想看,其实这个要求并不过分,action只是一个处理程序,不应该保存跨http请求的状态数据,按理来说,也应该做成线程安全的。
(2) template name到view template path的映射。
<forward name="success" path="/pages/welcome.jsp"/>
action类返回一个template name,actionservlet根据这个template name获得对应的view template path,然后调用
request.getrequestdispatcher(“view template path”),把处理转向路径对应的servlet。在这个例子中,是转向/pages/welcome.jsp编译后的servlet。
我们来看一个一个velocity的例子。
<include name="success" path="/pages/welcome.vm"/>
web.xml的定义如下
<servlet>
<servlet-name>velocity</servlet-name>
<servlet-class>org.apache.velocity.tools.view.servlet.velocityviewservlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>velocity</servlet-name>
<url-pattern>*.vm</url-pattern>
</servlet-mapping>
这时,request.getrequestdispatcher(“/pages/welcome.vm”)会调用velocityviewservlet,由velocityviewservlet负责装并驱动运行/pages/welcome.vm这个模板文件。
这里面有一个问题,如果调用的是dispatchrequester.include()方法,那么如何才能把pages/welcome.vm传给velocityviewservlet呢?
如前所说,requestdispatcher传递的参数只有两个,request和response。那么只能通过request attribute。正是为了解决这个问题,servlet2.3规范之后,加入了javax.servlet.include.servlet_path这个属性。
参见velocityviewservlet的代码(velocity-tool开源项目)
// if we get here from requestdispatcher.include(), getservletpath()
// will return the original (wrong) uri requested. the following special
// attribute holds the correct path. see section 8.3 of the servlet
// 2.3 specification.
string path = (string)request.getattribute("javax.servlet.include.servlet_path");
从这里我们可以看出,为什么通晓servlet specification对于通晓web framework至关重要。
(3) form bean的定义
如<form-bean name="logonform" type="app.logonform"/>
struts form bean需要继承actionform类。
form bean类,主要有三个作用:
[1]根据bean的定义,利用reflection机制,自动把request参数转化为需要的数据类型,填入到bean的属性当中。actionform类名中虽然有form这个词,但不仅能够获取form提交后的http post参数,也可以获取url后缀的http get参数。
[2]输入验证。用户可以配置validation.xml,定义各属性的验证规则。
[3]当作view object来用。用户需要熟练掌握struts html taglib的用法,才能把form bean的属性正确显示出来。
(4)其他定义。详见struts文档。不再赘述。
2.实现action。
action类从form bean或直接从request中获得输入参数,调用商业逻辑,把结果数据(也许会包装成view object),用request.setattribute()放到request中,最后返回一个用forwardmapping类包装的template name。
3.实现view。
struts view的标准实现方法是jsp + struts taglib,其中最重要的就是struts html taglib。
html:form tag则是整个html tag的核心,其它的如html:input, html:select等tag,都包含在html:form tag里面。
html:form tag用来映射form bean(也可以通过适当定义,映射其他的bean,但使用上会有很多麻烦)。html:form tag包含的其他struts html tag用来映射form bean的属性。
struts bean taglib的用法比较臃肿,一般情况下可以用jstl代替。当然,如果需要用到bean:message tag实现国际化,那又另当别论。
struts tile taglib用于页面布局。开源portal项目liferay使用了struts tile taglib做为布局控制。
4.高级扩展用法
用户可以重载struts的一些控制类,引入自己的一些定制类。详见struts文档。
本文不是struts专题,只讲述最重要的主流用法,其它边边角角的,不再赘述。
3. webwork
http://www.opensymphony.com/webwork/
webwork由于灵活的可插拔特性,受到很多资深程序员的欢迎。似乎很有可能大肆流行起来。
webwork项目建立在xwork项目上。入口servlet是webwork项目中定义的servletdispatcher,而action在xwork项目中定义。
xwork action接口的execute()方法没有参数,不像struts action那样接受request, response参数,所以xwork action能够脱离web环境被直接调用,便于单元测试。
这里引入了一个问题。没有了request参数,那么xwork action如何获得request parameters作为输入数据?又通过什么桥梁(struts用request.setattribute)把结果数据传送到view层?
在web work中,只能通过action本身的getter, setter属性来传送输入参数和输出结果。
比如,我们有这样一个实现了xwork action接口的类,
youraction implements action{
int productid = null;
string productname = null;
public void setproductid(int productid){this.productid = productid;}
public string getproductname(){return productname;}
public string execute(){
productname = findnamebyid(productid);
return “success”;
}
}
这个类里面的productid将接受request输入参数,productname是输出到页面显示的结果。
比如,这样的请求,http://yourhost/yourapp/myaction.action?productid=1
web work会把1填到youraction的productid里面,然后执行execute()方法,jsp里的语句<ww:property value=“productname”>会把youraction的productname显示在页面上。
如果一个web framework采用了这种屏蔽action的request, response参数的设计方式,一般也同时会采用这种action和输入输出数据结合成一体的解决方式。类似的情形也存在于tapestry和maverick中,后面会讲到。
当webwork servletdispatcher接收到http request的时候,首先把所有相关的信息(包括request, response, session, servlet config, servelt context, 所有request参数)等存放到acationcontext中,然后根据interceptor配置信息,生成一个youraction的动态代理类对象。实际上运行的正是这个代理对象,如同servlet filter的工作机制一般,所有注入的interceptor方法会先于actio方法运行。
我们来看一下action和interceptor的地位:action没有参数,无法获得actioncontext;而interceptor接受的actioninvoication参数拥有包括actioncontext在内的所有重要信息。
这种权力分配的不平等,注定了action的作用非常有限,只限于调用商业逻辑,然后返回一个成功与否标志。所有与外部web世界打交道、协调内部工作流程的重担,都责无旁贷地落在interceptor的肩上。
我们可以设想一个极端的例子。我们声明一批不做任何事情的空action,我们只是需要它们的空壳类名;我们制作一批对应的interceptor,所有的转发控制、商业逻辑都在interceptor上实现,然后把interceptor都注入到对应的空action。这在理论上是完全可行的。
在web海洋的包围中,action可少,interceptor不可少。action是一个孤岛,如果没有外来盟友interceptor的协助,只能在自己的小范围内独立作战(比如unit test),而对整体大局的作战目标无法产生影响。
下面我们来看一下action是如何在interceptor的全程监管下工作的。
在webwork中,我们需要如下配置xwork.xml。
<xwork>
<!– include webwork defaults (from webwork-2.1 jar). –>
<include file="webwork-default.xml" />
<!– configuration for the default package. –>
<package name="default" extends="webwork-default">
<!– default interceptor stack. –>
<default-interceptor-ref name=" defaultstack" />
<!– action: youraction. –>
<action name="youraction" class="yourapp.youraction">
<result name="success" type="dispatcher">
youraction.jsp
</result>
</action>
</package>
</xwork>
webwork-default.xml里面的相关定义如下:
<interceptors>
<interceptor name="validation" class="com.opensymphony.xwork.validator.validationinterceptor"/>
<interceptor name="static-params" class="com.opensymphony.xwork.interceptor.
staticparametersinterceptor"/>
<interceptor name="params" class="com.opensymphony.xwork.interceptor.parametersinterceptor
"/>
<interceptor name="conversionerror" class="com.opensymphony.webwork.interceptor.
webworkconversionerrorinterceptor"/>
<interceptor-stack name="defaultstack">
<interceptor-ref name="static-params"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionerror"/>
</interceptor-stack>
</interceptors>
从上述的配置信息中可以看出,youraction执行execute()方法的前后,会被
defaultstack所定义的三个intercepter截获。这些interceptor的任务之一就是把输入参数设置到action的对应属性当中。
如果我们需要加入对youraction的属性的验证功能,只要把上述定义中的validation interceptor加入到defaultstack中就可以了。当然,实际工作还没有这么简单,一般来说,还要为每个进行属性验证的action的都配置一份validation.xml。
xwork interceptor能够在package和action级别上,进行截获处理。
servlet filter能够在url patten级别上,进行截获处理。虽然实际上,servlet filter截获的是servlet,但某些情况下,可以达到和截获一批action的同样效果。
比如,在web work中,我们可以为所有admin package的action,加入一个interceptor,当检查到当前session的用户没有admin权限时,统一返回一个警告页面:您没有足够的权限执行这个操作。
我们看到也可以为所有url pattern为“admin/*.action”的url定义一个servlet filter,当检查到当前session的用户没有admin权限时,统一返回一个警告页面:您没有足够的权限执行这个操作。
webwork的interceptor配置是相当灵活的,相当于对action实现了aop。interceptor相当于aspect,基类aroundinterceptor的before(), after()方法相当于advice。
另外,xwork也提供了从xml配置文件装配component的机制,相当于实现了对于component的ioc。
提到aop和ioc,顺便多讲两句。spring aop能够截获所有interface,不限于某个特定接口;spring框架支持所有类型的ioc,不限于某种特定类型。
要知道,aop, ioc可是现在最时髦的东西,一定不要错过啊。:d
相关概念导读(如果需要,请用如下关键字搜索网络):
aop — aspect oriented programming — 面向方面编程。
ioc – inversion of control –控制反转
dynamic proxy — 动态代理,jdk1.4引入的特性。还可以进一步参考cglib, asm等开源项目。
webwork直接支持所有主流view — xsl,velocity, freemarker,jsp。webwork还提供了自己的taglib。“直接支持”的意思是说,不用像struts那样,使用velocity的时候,还需要引入辅助桥梁velocity-tool。
webwork中用到一种功能和xpath类似的对象寻径语言ongl,是一个开源项目。ongl同样用在下面要介绍的tapestry项目中。
opensymphony下还有一个sitemesh项目,通过servlet filter机制控制布局。可以和webwork组合使用。
4. tapestry
http://jakarta.apache.org/tapestry/
tapestry近来突然火了起来,令我感到吃惊。也许是jsf带来的page component风潮令人们开始关注和追逐tapestry。
tapestry的重要思想之一就是page component。
前面讲到,xwork能够自动把request参数映射到action的属性当中。tapestry走得更远,甚至能够根据request参数,映射到action(tapestry里面称为page)的方法,并把request参数映射为page方法需要的参数,进行正确的调用。就这样,tapestry不仅把输入输出数据,而且把事件方法也绑定到了page上面。
在tapestry框架中,action的概念已经非常模糊,而换成了page的概念。而tapestry page是拥有属性和事件的页面组件,其中的事件处理部相当于action的职责,而属性部分起着model的作用。
除了使用page和其它的tapestry页面组件,用户也可以自定义页面组件。
这种页面组件/属性事件的编程模型,受到一些程序员的欢迎。当然,这种编程模型并不是没有代价的,每个tapestry模板文件都需要一个对应的.page文件。这些.page文件定义了页面组件的属性、事件、validator等信息。
我们来看一下b/s结构中,组件的属性、事件和http request绑定的基本原理。一个能够发出请求的页面组件(比如link和button),在输出自己的html的时候,需要输出一些特殊的信息来标志本组件的属性/事件,这样下次http request来的时候,会把这些信息带回来,以便web framework加以辨认识别,发给正确的page component处理。
这些特殊信息通常包含在url参数或hidden input里面,必要的时候,还需要生成一些java script。tapestry,echo,jsf都是这种原理。
tapestry的例子如下:
<a href="#" jwcid="@directlink" parameters="ognl:currentitem.itemid" listener="ognl:listeners.showitem">
jsf用taglib实现页面组件,也提供了类似的commandlink和commandbutton tag。其中对应tapestry listener的tag属性是action。后面会讲解。
tapestry的模板标签是html标签的扩展,具有良好的“所见即所得”特性,能够直接在浏览器中正确显示,这也是tapestry的一个亮点。
5. echo
http://sourceforge.net/projects/echo
echo提供了一套类似swing的页面组件,直接生成html。
从程序员的角度看来,用echo编写web程序,和用swing编写applet一样,属于纯面向组件事件编程,编程模型也以event/listener结构为主体。
echo没有dispatcher servlet,也没有定义url->action映射的配置文件。
echo的action就是实现了actionlistener接口(参数为actionevent)的servlet(继承echoserver类)。
所以,echo直接由web server根据web.xml配置的url -> servlet的映射,进行转发控制。
echo也没有明显的view层,echo在页面组件方面走得更远,所有的html和javascript都由框架生成。你不必(也没有办法)写html,只需要(也只能)在java代码中按照类似swing编程方式,生成或操作用户界面。用户也可以定制自己的echo组件。
echo的ui component的实现,采用了两个重要的模式。一个是peer(component -> componentpeer)模式,一个是ui component -> renderer模式。
虽然echo的api更类似于swing,但实现上却采用更接近于awt的peer模式。每个component类(代表抽象的组件,比如button),都有一个对应的componentpeer类(代表实际的组件,比如windows桌面的button,linux桌面的button,html button等)。
先别急,这个事情还没有完。虽然componentpeer落实到了具体的界面控件,但是它还是舍不得显示自己,进一步把显示工作交给一个renderer来执行。
比如,在echo里面,button类对应一个buttonui(继承了componentpeer)类,而这个buttonui类会把最终显示交给buttonrender来处理。
据说多了这么一步,能够让显示控制更加灵活丰富。比如,同一个renderer可以处理不同的ui component,同一个ui component也可以交给不同的renderer处理。
jsf的页面组件也采用了ui component -> renderer模式,后面会讲到。
6. jsf
http://java.sun.com/j2ee/javaserverfaces/index.jsp
http://wwws.sun.com/software/communitysource/jsf/download.html download source
jsf的中心思想也是页面组件/属性事件。一般来说,jsf的页面组件是一个三件套{ ui component, tag, renderer}。
ui component有可能对应model,event,listener。tag包含componenttype和renderertype两个属性,用来选择对应的的ui component和renderer。
jsf的应用核心无疑是jsf taglib。jsf taglib包含了对应所有重要html元素的tag,而且input tag可以直接包含validator tag或者validator属性,来定义验证手段。
我们通过jsf携带的cardemo例子,来看jsf的处理流程。
(1) cardetail.jsp有如下内容:
<h:commandbutton action="#{carstore.buycurrentcar}" value="#{bundle.buy}" />
可以看到,这个button的submit action和carstore.buycurrentcar方法绑定在一起。我们在tapestry里面曾经看到过类似的情景。
(2) carstore在faces-config.cml中定义:
<managed-bean>
<managed-bean-name> carstore </managed-bean-name>
<managed-bean-class> carstore.carstore </managed-bean-class>
<managed-bean-scope> session </managed-bean-scope>
</managed-bean>
(3) carstore.carstore类中的buycurrentcar方法如下:
public string buycurrentcar() {
getcurrentmodel().getcurrentprice();
return "confirmchoices";
}
(4) confirmchoices转向在faces-config.cml中定义:
<navigation-rule>
<from-view-id>/cardetail.jsp</from-view-id>
<navigation-case>
<description>
any action that returns "confirmchoices" on cardetail.jsp should
cause navigation to confirmchoices.jsp
</description>
<from-outcome>confirmchoices</from-outcome>
<to-view-id>/confirmchoices.jsp</to-view-id>
</navigation-case>
</navigation-rule>
(5)于是转到页面confirmchoices.jsp。
除了interceptor之外,jsf几乎包含了现代web framework应该具备的所有特性:页面组件,属性事件,ioc (managedbean),component -> renderer,类似于swing component的model-event-listener。
也许设计者认为,众多庞杂的模式能够保证jsf成为一个成功的框架。portal开源项目exo就是建立在jsf框架上。
可以看出这样一个趋势,现代web framework认为b/s结构的无状态特性和html界面是对编程来说是需要极力掩盖的一个缺陷,所以尽量模拟c/s结构的组件和事件机制,以吸引更多的程序员。
7. maverick
http://mav.sourceforge.net/
maverick是一个轻量而完备的mvc model 2框架。maverick的action不叫action,直截了当的称作controller。
controller只接受一个controllercontext参数。request,response, servlet config, servelt context等输入信息都包装在controllercontext里面,而且model也通过controllercontext的model属性返回。整个编程结构清晰而明快,令人赞赏。
但这个世界上难有十全十美的事情,由于controllercontext只有一个model属性可以传递数据,程序员必须把所有需要的数据都打包在一个对象里面设置到model属性里。这种麻烦自然而然会导致这样的可能用法,直接把controller本身设置为model,这又回到了controller(action)和model一体的老路。
前面讲到,webwork也把所有的输入信息都包装在actioncontext里面,但action并没有权力获取。而在maverick中,controller对于controllercontext拥有全权的控制,两者地位不可同日而语。当然,由于参数controllercontext包含request,reponse之类信息,这也意味着,maverick controller不能像webwork action那样脱离web环境独立运行。
当然,这也并不意味着任何结构性缺陷。程序的结构由你自己控制,你完全可以把需要unit test的那部分从web环境脱离开来,放到business层。
如同webwork,maverick直接支持所有的主流view。maverick的配置文件采struts, cocoon两家之长,url -> action -> view映射的主体结构类似于struts,而view定义部分对transform的支持则类似于cocoon。如:
<command name="friends">
<controller class="org.infohazard.friendbook.ctl.friends"/>
<view name="success" path="friends.jsp">
<transform path="triminside.jsp"/>
</view>
</command>
8. spring mvc
http://www.springframework.com/
spring mvc是我见过的结构最清晰的mvc model 2实现。
action不叫action,准确地称做controller;controller接收request, response参数,干脆利落地返回modelandview(其中的model不是object类型,而是map类型)。
其它的web framework中, action返回值一般都只是一个view name;model则需要通过其它的途径(如request.attribute,context参数,或action本身的属性数据)传递上去。
spring以一招ioc名满天下,其aop也方兴未艾。“spring出品,必属精品”的观念已经深入人心。我这里多说也无益,强烈建议读者去阅读spring doc & sample & code本身。
9. turbine
http://jakarta.apache.org/turbine/
turbine是一个提供了完善权限控制的坚实框架(fulcrum子项目是其基石)。turbine的个人用户不多,但不少公司用户选择turbine作为框架,开发一些严肃的应用(我并没有说,用其它框架开发的应用就不严肃^_^)。portal开源项目jetspeed建立在turbine上。
turbine用rundata来传递输入输出数据。如同maverick的controllercontext,rundata是整个turbine框架的数据交换中心。除了request, response等基本信息,rundata直接包括了user/acl等权限控制相关的属性和方法,另外还包括action name和target template name等定位属性。
module是turbine里面除了rundata之外的又一个核心类,是turbine框架的基本构件,action是module,screen也是module。turbine提供了loginuser和logoutuser两个action作为整个系统的出入口。而其余流量的权限控制则由类似于servlet filter机制的pipeline控制。
turbine pipeline的编程模型和servlet filter一模一样:turbine pipeline的valve就相当于servlet filter,而valvecontext则相当于filter chain。还有更相近的例子,tomcat源代码里面也有valve和valuecontext两个类,不仅编程模型一样,而且名字也一样。
权限控制贯穿于turbine框架的始终。要用好turbine,首先要通晓子项目fulcrum 的security部分的权限实现模型。
fulcrum security的权限实体包括四个– user, group, role, permission。
实体之间包含{role,permission}和{ group, user, role}两组关系。
{role,permission}是多对多的关系,一个role可以具有各种permission;{ group, user, role}之间是多对多的关系,一个group可包含多个user,并可以给user分配不同的role。
权限模型的实现同样采用peer模式,entity -> entitypeer, entity -> managerpeer。
entity和entitymanger代表抽象的模型概念,而entitypeer和managerpeer代表具体的实现。
用户可以根据模型,提供不同的实现,比如,用内存结构中实现,用数据表结构实现,与windows nt权限验证机制结合,与osworkflow的权限控制模型结合,等等。其中,用数据表结构实现,又可以选择用torque实现,或者用hibernate实现。(torque是turbine的o/r mapping子项目)
例如,falcrum.property配置文件包含如下security相关选项:
# ——————————————————————-
# s e c u r i t y s e r v i c e
# ——————————————————————-
services.securityservice.user.class=org.apache.fulcrum.security.impl.db.entity.turbineuser
services.securityservice.user.manager=org.apache.fulcrum.security.impl.db.dbusermanager
services.securityservice.secure.passwords.algorithm=sha
# ——————————————————————-
# d a t a b a s e s e r v i c e
# ——————————————————————-
services.databaseservice.database.newapp.driver=org.gjt.mm.mysql.driver
services.databaseservice.database.newapp.url=jdbc:mysql://127.0.0.1/newapp
services.databaseservice.database.newapp.username=turbine
services.databaseservice.database.newapp.password=turbine
这说明,权限控制实现由数据库提供,需要根据权限模型创建如下数据表:
turbine_user,turbine_role,turbine_group,
turbine_permission,turbine_role_permission,
turbine_user_group_role。
10. cocoon
http://cocoon.apache.org
cocoon项目是一个叫好不叫做的框架。采用xml + xslt pipeline机制,java程序只需要输出xml数据,cocoon框架调用xsl文件把xml数据转换成html、wml等文件。
cocoon强大灵活的xsl pipeline配置功能,xslt的内容/显示分离的承诺,一直吸引了不少程序员fans。怎奈天不从人愿,由于复杂度、速度瓶颈、xsl学习难度等问题的限制,cocoon一直主要限于网站发布出版领域,向cms和portal方向不断发展。另外,cocoon开发了xsp脚本和cocoon form技术。
cocoon的sitemap.xmap配置文件比较复杂,与其它的web framework差别很大。
主体pipelines配置部分采用pattern match的方式,很像xsl语法,也可以类比于web.xml里面servlet mapping的定义。比如,一个典型的url->action的映射定义看起来是这个样子:
<map:pipelines>
<map:pipeline>
<map:match pattern="*-dept.html">
<map:act set="process">
<map:parameter name="descriptor"
value="context://docs/department-form.xml"/>
<map:parameter name="form-descriptor"
value="context://docs/department-form.xml"/>
<map:generate type="serverpages" src="docs/confirm-dept.xsp"/>
<map:transform src="stylesheets/apache.xsl"/>
<map:serialize/>
</map:act>
<map:generate type="serverpages" src="docs/{1}-dept.xsp"/>
<map:transform src="stylesheets/apache.xsl"/>
<map:serialize/>
</map:match>
</map:pipeline>
</map:pipelines>
11. barracuda
http://barracudamvc.org/barracuda/index.html
barracuda是一个html dom component + event/listener结构的框架。
根据模板文件或配置文件生成静态java类,并在代码中使用这些生成类,是barracuda的一大特色。
barracuda需要用xmlc项目把所有的html或wml模板文件,静态编译成dom结构的java类,作为页面组件。xmlc会根据html元素的id定义,生成相应dom结点的简便操作方法。
barracuda的事件类也需要用barracuda event builder工具把event.xml编译成java类,引入到工程中。barracuda直接用java类的继承关系映射事件之间的父子层次关系。比如,childevent是parentevent的子类。
barracuda的事件分为两类:request events(control events)和response events(view events)。
barracuda事件处理过程很像windows系统消息队列的处理机制。
(1) barracuda根据http request生成request event,放入到事件队列中。
(2) eventdispatcher检查事件队列是否为空,如果为空,结束。如果非空,按照先进先出的方式,从事件队列中取出一个事件,根据这个事件的类型,选择并调用最合适的eventlistener,参数event context包含事件队列。
“根据事件类型,选择最合适的eventlistener对象”的过程是这样的:比如,
eventdispatcher从时间队列里取出来一个事件,类型是childevent;barracuda首先寻找注册了监听childevent的eventlistener,如果找不到,再上溯到childevent的父类parentevent,看哪些eventlistener对parentevent感兴趣。
详细过程参见barracuda的defaulteventdispatcher类。
(3) eventlistener根据event context包含的request信息,调用商业逻辑,获得结果数据,然后根据不同情况,把新的事件加入到event context的事件队列中。
(4) 控制交还给eventdispatcher,回到第(2)步。
the end.
enjoy.
