本文介绍
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.
posted on 2006-08-16 19:59
阿成 阅读(170)
评论(0) 编辑 收藏