本文介绍 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.
|
|