Written by
王海龙
buaawhl@sina.com
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
程序工作原理
[
编者按:本部分内容在本期杂志
《Servlet规范简介》有更详细介绍
]
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">
[
编者按:OGNL是一种利用java对象setter和getter方法来访问其属性的表达式语言,Tepestry项目及很多项目使用了该技术。更详细链接http://www.ognl.org/]
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.