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.
		
		
				
						 
				
		
		
		
		Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=252039