Java Web Framework综述

Java Web Framework综述

0.简介

本文介绍Java Web Framework的基本工作原理,和一些常用的开源Web MVC Framework(Struts, Web Work, Tapestry, Echo, JSF, Maverick, Spring MVC, Turbine, Cocoon, Barracuda)

Web开发的最重要的基本功是HTTPJava Web开发的最重要的基本功是Servlet SpecificationHTTPServlet Specification对于Web ServerWeb Framework的开发实现来说,是至关重要的协议规范。

应用和剖析开源Web Framework,既有助于深入掌握HTTP & Servlet Specification, 也有助于了解一些现代的B/S Web框架设计思想,如MVC,事件处理机制,页面组件,IoCAOP等。在这个现代化的大潮中,即使Servlet规范本身也不能免俗,不断引入FilterListener等现代框架设计模式。同是Sun公司出品的JSF更是如此。

关于MVC模型、项目简介、配置文件、入门示例等基础知识,网上已经有大量的重复资料信息,本文不再赘述。

文中会提到一些相关的开源项目,和一些编程思想,如有需要,可以用相关的关键字在网上搜索,获取基本的背景知识。

本文力图言简意赅,突出重点。着重描述其他资料没有提到、或很少提到的较重要内容,如运行原理、主流用法,相关知识,关键特性等。

1. Java Web程序工作原理

TomcatServer.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)。所以,requestsetAttribute()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,把处理转交给ViewJSP +TagLib, Velocity, Free Marker, XSL等)。

View一般通过request.getAttribute()获得结果数据,并显示到客户端。至于是谁把结果数据设置到request.attribute里面,有两种可能:ActionDispatcher 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 PathAction的映射。

<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 NameView 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权限时,统一返回一个警告页面:您没有足够的权限执行这个操作。

 

WebWorkInterceptor配置是相当灵活的,相当于对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参数,映射到ActionTapestry里面称为Page)的方法,并把request参数映射为Page方法需要的参数,进行正确的调用。就这样,Tapestry不仅把输入输出数据,而且把事件方法也绑定到了Page上面。

Tapestry框架中,Action的概念已经非常模糊,而换成了Page的概念。而Tapestry Page是拥有属性和事件的页面组件,其中的事件处理部相当于Action的职责,而属性部分起着Model的作用。

除了使用Page和其它的Tapestry页面组件,用户也可以自定义页面组件。

 

这种页面组件/属性事件的编程模型,受到一些程序员的欢迎。当然,这种编程模型并不是没有代价的,每个Tapestry模板文件都需要一个对应的.page文件。这些.page文件定义了页面组件的属性、事件、Validator等信息。

 

我们来看一下B/S结构中,组件的属性、事件和HTTP Request绑定的基本原理。一个能够发出请求的页面组件(比如LinkButton),在输出自己的HTML的时候,需要输出一些特殊的信息来标志本组件的属性/事件,这样下次HTTP Request来的时候,会把这些信息带回来,以便Web Framework加以辨认识别,发给正确的Page Component处理。

这些特殊信息通常包含在URL参数或Hidden Input里面,必要的时候,还需要生成一些Java ScriptTapestryEchoJSF都是这种原理。

Tapestry的例子如下:

<a href="#" jwcid="@DirectLink" parameters="ognl:currentItem.itemId" listener="ognl:listeners.showItem">

JSFTagLib实现页面组件,也提供了类似的CommandLinkCommandButton Tag。其中对应Tapestry listenerTag属性是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映射的配置文件。

EchoAction就是实现了ActionListener接口(参数为ActionEvent)的Servlet(继承EchoServer类)。

所以,Echo直接由Web Server根据web.xml配置的URL -> Servlet的映射,进行转发控制。

 

Echo也没有明显的View层,Echo在页面组件方面走得更远,所有的HTMLJavaScript都由框架生成。你不必(也没有办法)写HTML,只需要(也只能)在Java代码中按照类似Swing编程方式,生成或操作用户界面。用户也可以定制自己的Echo组件。

EchoUI Component的实现,采用了两个重要的模式。一个是PeerComponent -> ComponentPeer)模式,一个是UI Component -> Renderer模式。

虽然EchoAPI更类似于Swing,但实现上却采用更接近于AWTPeer模式。每个Component类(代表抽象的组件,比如Button),都有一个对应的ComponentPeer类(代表实际的组件,比如windows桌面的ButtonLinux桌面的ButtonHTML 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有可能对应ModelEventListenerTag包含componentTyperendererType两个属性,用来选择对应的的UI ComponentRenderer

JSF的应用核心无疑是JSF TagLibJSF TagLib包含了对应所有重要HTML元素的Tag,而且Input Tag可以直接包含Validator Tag或者Validator属性,来定义验证手段。

 

我们通过JSF携带的cardemo例子,来看JSF的处理流程。

(1) carDetail.jsp有如下内容:

<h:commandButton action="#{carstore.buyCurrentCar}" value="#{bundle.buy}" />

可以看到,这个buttonsubmit actioncarstore.buyCurrentCar方法绑定在一起。我们在Tapestry里面曾经看到过类似的情景。

 

(2) carstorefaces-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 ComponentModel-Event-Listener

也许设计者认为,众多庞杂的模式能够保证JSF成为一个成功的框架。Portal开源项目eXo就是建立在JSF框架上。

 

可以看出这样一个趋势,现代Web Framework认为B/S结构的无状态特性和HTML界面是对编程来说是需要极力掩盖的一个缺陷,所以尽量模拟C/S结构的组件和事件机制,以吸引更多的程序员。

7. Maverick

http://mav.sourceforge.net/

Maverick是一个轻量而完备的MVC Model 2框架。MaverickAction不叫Action,直截了当的称作Controller

Controller只接受一个ControllerContext参数。requestresponse, servlet config, servelt context等输入信息都包装在ControllerContext里面,而且Model也通过ControllerContextmodel属性返回。整个编程结构清晰而明快,令人赞赏。

但这个世界上难有十全十美的事情,由于ControllerContext只有一个model属性可以传递数据,程序员必须把所有需要的数据都打包在一个对象里面设置到model属性里。这种麻烦自然而然会导致这样的可能用法,直接把Controller本身设置为model,这又回到了Controller(Action)Model一体的老路。

 

前面讲到,WebWork也把所有的输入信息都包装在ActionContext里面,但Action并没有权力获取。而在Maverick中,Controller对于ControllerContext拥有全权的控制,两者地位不可同日而语。当然,由于参数ControllerContext包含requestreponse之类信息,这也意味着,Maverick Controller不能像WebWork Action那样脱离Web环境独立运行。

当然,这也并不意味着任何结构性缺陷。程序的结构由你自己控制,你完全可以把需要Unit Test的那部分从Web环境脱离开来,放到Business层。

如同WebWorkMaverick直接支持所有的主流ViewMaverick的配置文件采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,准确地称做ControllerController接收request, response参数,干脆利落地返回ModelAndView(其中的Model不是Object类型,而是Map类型)。

其它的Web Framework中, Action返回值一般都只是一个View NameModel则需要通过其它的途径(如request.attributeContext参数,或Action本身的属性数据)传递上去。

 

Spring以一招IoC名满天下,其AOP也方兴未艾。“Spring出品,必属精品”的观念已经深入人心。我这里多说也无益,强烈建议读者去阅读Spring Doc & Sample & Code本身。

9. Turbine

http://jakarta.apache.org/turbine/

Turbine是一个提供了完善权限控制的坚实框架(Fulcrum子项目是其基石)。Turbine的个人用户不多,但不少公司用户选择Turbine作为框架,开发一些严肃的应用(我并没有说,用其它框架开发的应用就不严肃^_^)。Portal开源项目JetSpeed建立在Turbine上。

TurbineRunData来传递输入输出数据。如同MaverickControllerContextRunData是整个Turbine框架的数据交换中心。除了request, response等基本信息,RunData直接包括了User/ACL等权限控制相关的属性和方法,另外还包括Action NameTarget Template Name等定位属性。

ModuleTurbine里面除了RunData之外的又一个核心类,是Turbine框架的基本构件,ActionModuleScreen也是ModuleTurbine提供了LoginUserLogoutUser两个Action作为整个系统的出入口。而其余流量的权限控制则由类似于Servlet Filter机制的Pipeline控制。

Turbine Pipeline的编程模型和Servlet Filter一模一样:Turbine PipelineValve就相当于Servlet Filter,而ValveContext则相当于Filter Chain。还有更相近的例子,Tomcat源代码里面也有ValveValueContext两个类,不仅编程模型一样,而且名字也一样。

 

权限控制贯穿于Turbine框架的始终。要用好Turbine,首先要通晓子项目Fulcrum Security部分的权限实现模型。

Fulcrum Security的权限实体包括四个-- User, Group, Role, Permission

实体之间包含{RolePermission}{ Group, User, Role}两组关系。

{RolePermission}是多对多的关系,一个Role可以具有各种Permission{ Group, User, Role}之间是多对多的关系,一个Group可包含多个User,并可以给User分配不同的Role

权限模型的实现同样采用Peer模式,Entity -> EntityPeer, Entity -> ManagerPeer

EntityEntityManger代表抽象的模型概念,而EntityPeerManagerPeer代表具体的实现。

用户可以根据模型,提供不同的实现,比如,用内存结构中实现,用数据表结构实现,与Windows NT权限验证机制结合,与OSWorkflow的权限控制模型结合,等等。其中,用数据表结构实现,又可以选择用Torque实现,或者用Hibernate实现。(TorqueTurbineO/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_USERTURBINE_ROLETURBINE_GROUP

TURBINE_PERMISSIONTURBINE_ROLE_PERMISSION

TURBINE_USER_GROUP_ROLE

 

10. Cocoon

http://cocoon.apache.org

Cocoon项目是一个叫好不叫做的框架。采用XML + XSLT Pipeline机制,Java程序只需要输出XML数据,Cocoon框架调用XSL文件把XML数据转换成HTMLWML等文件。

Cocoon强大灵活的XSL Pipeline配置功能,XSLT的内容/显示分离的承诺,一直吸引了不少程序员fans。怎奈天不从人愿,由于复杂度、速度瓶颈、XSL学习难度等问题的限制,Cocoon一直主要限于网站发布出版领域,向CMSPortal方向不断发展。另外,Cocoon开发了XSP脚本和Cocoon Form技术。

Cocoonsitemap.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项目把所有的HTMLWML模板文件,静态编译成DOM结构的Java类,作为页面组件。XMLC会根据HTML元素的id定义,生成相应DOM结点的简便操作方法。

 

Barracuda的事件类也需要用Barracuda Event Builder工具把event.xml编译成Java类,引入到工程中。Barracuda直接用Java类的继承关系映射事件之间的父子层次关系。比如,ChildEventParentEvent的子类。

Barracuda的事件分为两类:Request EventsControl Events)和Response EventsView Events)。

 

Barracuda事件处理过程很像Windows系统消息队列的处理机制。

(1) Barracuda根据HTTP Request生成Request Event,放入到事件队列中。

(2) EventDispatcher检查事件队列是否为空,如果为空,结束。如果非空,按照先进先出的方式,从事件队列中取出一个事件,根据这个事件的类型,选择并调用最合适的EventListener,参数Event Context包含事件队列。

 “根据事件类型,选择最合适的EventListener对象”的过程是这样的:比如,

EventDispatcher从时间队列里取出来一个事件,类型是ChildEventBarracuda首先寻找注册了监听ChildEventEventListener,如果找不到,再上溯到ChildEvent的父类ParentEvent,看哪些EventListenerParentEvent感兴趣。

详细过程参见BarracudaDefaultEventDispatcher类。

(3) EventListener根据Event Context包含的request信息,调用商业逻辑,获得结果数据,然后根据不同情况,把新的事件加入到Event Context的事件队列中。

(4) 控制交还给EventDispatcher,回到第(2)步。

 

The End.

Enjoy.

你可能感兴趣的:(Java Web Framework综述)