BloveSaga

在希腊帕尔纳斯山南坡上,有一个驰名世界的戴尔波伊神托所,在它的入口处的巨石上赫然锈刻着这样几个大字: 认识你自己!

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  34 随笔 :: 12 文章 :: 122 评论 :: 0 Trackbacks

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 的入口ServletActionServlet

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 NameActionServlet根据这个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传递的参数只有两个,requestresponse。那么只能通过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:selecttag,都包含在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项目上。入口ServletWebWork项目中定义的ServletDispatcher,而ActionXWork项目中定义。

XWork Action 接口的execute()方法没有参数,不像Struts Action那样接受request, response参数,所以XWork Action能够脱离Web环境被直接调用,便于单元测试。

这里引入了一个问题。没有了request参数,那么XWork Action如何获得request parameters作为输入数据?又通过什么桥梁(Strutsrequest.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填到YourActionproductId里面,然后执行execute()方法,JSP里的语句<ww:property value=“productName”>会把YourActionproductName显示在页面上。

 

如果一个Web Framework采用了这种屏蔽Actionrequest, response参数的设计方式,一般也同时会采用这种Action和输入输出数据结合成一体的解决方式。类似的情形也存在于TapestryMaverick中,后面会讲到。

WebWork ServletDispatcher接收到HTTP Request的时候,首先把所有相关的信息(包括request, response, session, servlet config, servelt context, 所有request参数)等存放到AcationContext中,然后根据Interceptor配置信息,生成一个YourAction的动态代理类对象。实际上运行的正是这个代理对象,如同Servlet Filter的工作机制一般,所有注入的Interceptor方法会先于Actio方法运行。

我们来看一下ActionInterceptor的地位: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 能够在PackageAction级别上,进行截获处理。

Servlet Filter 能够在URL Patten级别上,进行截获处理。虽然实际上,Servlet Filter截获的是Servlet,但某些情况下,可以达到和截获一批Action的同样效果。

比如,在Web Work中,我们可以为所有admin packageAction,加入一个Interceptor,当检查到当前Session的用户没有admin权限时,统一返回一个警告页面:您没有足够的权限执行这个操作。

我们看到也可以为所有URL Pattern为“admin/*.action”的URL定义一个Servlet Filter,当检查到当前Session的用户没有admin权限时,统一返回一个警告页面:您没有足够的权限执行这个操作。

 

WebWork Interceptor配置是相当灵活的,相当于对Action实现了AOPInterceptor相当于Aspect,基类AroundInterceptorbefore(), after()方法相当于Advice

另外,XWork也提供了从XML配置文件装配Component的机制,相当于实现了对于ComponentIoC

提到AOPIoC,顺便多讲两句。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,JSPWebWork还提供了自己的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对象settergetter方法来访问其属性的表达式语言,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.

 

posted on 2006-06-18 10:37 蓝色Saga 阅读(162) 评论(0)  编辑  收藏 所属分类: STRUTS

只有注册用户登录后才能发表评论。


网站导航: