在Jsp Model 2模型中, 用户的所有请求提交给Controller Servlet,
由Controller进行统一分配, 并且采用推的方式将不同的UI显示给用户。
这种推方式在很多人看来是一种优点,因为在Struts等MVC实现中具体推送的UI可以在配置文件中配置,配置完成后还可以通过一些可视化分析工具得到
整个站点地图。在Model2模式中基本的访问格式为:
action.do?其他参数
我
本人从未应用过Model2模式,但与我们的jsplet框架对比,我认为这种推送方式在大多数情况下并不是什么优点。如果将一次web访问看作是一次函
数调用,则按照Model2模式,这个函数的返回情况是不确定的,需要由一个额外的配置文件来确定。而我们知道,一个返回情况不确定的函数一般不是什么良
好的设计。在我们的框架设计中,一个基本的观点是尽量将自由度暴露给实际控制它的人。实际上,在大多数情况下,页面编制人员知道应该使用哪个页面来显示数
据,他们并不需要一个额外的配置文件。Jsplet使用如下的url格式:
视图jsp?objectName=模型对象名&objectEvent=响应事件名&其他参数
举一个具体的例子:
http://my.com/demo_view.jsp?objectName=/@Demo&objectEvent=test
demo_view.jsp是指定的显示页面, 其代码如下:
[code]
<%@ include file = "/engine.jsp" %>
<!-- 相当于在jsp模型中增加了一个新的变量thisObj,从而实现jsp页面的对象化 -->
<c:out>${thisObj.testVar}</c:out>
[/code]
objectName被WebEngine映射到session中的一个对象,在demo_view.jsp中成为thisObj这个变量,这就相当于java语言中的this指针,从而实现了jsp页面的对象化。
WebEngine还将objectEvent映射到一个Action响应函数并自动调用它,具体的Action代码写在一个独立的java文件或者jsp文件中。
DemoAction.jsp
[code]
<%@ include file = "/jsp_action_begin.jsp" %>
<%!
//
// objectName映射为thisObj, objectEvent=test映射对actTest的调用
// 在这里增加一个actXXX函数之后,即可通过objectEvent=XXX来访问,不需要任何配置
public Object actTest(){
// thisObj中的变量可以在视图中使用
thisObj.set("testVar","hello");
return success();
}
// 如果存在actBeforeAction函数,则该函数在所有action函数之前调用
public Object actBeforeAction(){
return success();
}
// 如果存在actAfterAction函数,则该函数在所有action函数之后调用
public Object actAfterAction(){
return success();
}
%>
<%@ include file="/jsp_action_end.jsp" %>
[/code]
在Jsplet框架中只需要注册对象,而不需要单独注册每个action。
register.jsp
[code]
<%
WebEngine.registerType("Demo", new WebActionType("/demo/action/DemoAction.jsp"),pageContext);
%>
[/code]
与Jsplet
框架对比,Model2是对action的建模而不是对object的建模,即它相当于将objectName,objectEvent和
view.jsp绑定在一起定义为一个访问点action.do,绑定过程中需要一个配置文件来固化view.jsp和action之间的联系。因此,
Model2并没有完全分离view和model,它隐含假定着objectName只具有一个objectEvent,
并且绑定了一个具体的view(出错页面除外)。
例如, 我们需要两个不同的view来显示同一个数据,则在Model2程序中可能需要配置两个独立的访问点,而在我们的框架中只需要使用两个不同的url:
a_view.jsp?objectName=/@Demo&objectEvent=test
b_view.jsp?objectName=/@Demo&objectEvent=test
同样的web程序甚至可以在前台通过XMLHTTP方式来调用而不需要额外配置!
在Jsplet框架中采用的是对象化的方式而不是Action化的方式,因此存在着多种面向对象的扩展,而所有的扩展都直接体现在url格式的细化上,一切都在阳光下。
在Jsplet中objectName是WebObject的名称,在全系统内唯一,其格式定义为: objectScope@objectType$objectInstanceId
1. 对象类型objectType
我们需要注册的是对象类型而不是完整的对象名,一个对象类型可以对应于无数个完整的对象名,例如我们注册了Demo类型的WebObject, 则objectName=/@Demo和objectName=/left/@Demo对应的处理文件都是DemoAction.jsp。
2. 对象生命周期控制objectScope
objectScope为WebObject所在的域,其格式符合Unix路径命名规范。JSP模型本身支持一些预定义的对象域,包括page,
request, session,
application等。但为了能够反映现实世界中的对象组织结构,对象域必须是允许自定义的。objectScope被组织成一个树形结构,这是一个
基本的控制结构,其控制策略为
同时存在的对象域之间必须存在线性序关系(order)
当系统访问某一对象时,如果该对象所在的对象域不能和现有对象的域处在同一"路径"下(即当对象域之间不能建立父子关系时),系统就会自动销毁不兼容路径
分支下的所有对象。 这种精细的控制策略保证了系统的可扩展性,因为模型上可以保证始终只有一部分对象被创建。
对象转移
系统动作
/main/@MyObject ==> /main/left/@OtherObject
无
/main/left/@OtherObject ==> /main/@MyObject
无
/main/left/@OtherObject ==> /main/left/@MyObject 无
/main/left/@OtherObject ==> /main/right/@MyObject 自动销毁/main/left子域下的对象,如/main/left/@OtherObject
3. 对象实例标识 objectInstanceId
如果在某一对象域中需要包含多个同一类型的对象,可以通过objectInstanceId来加以区分,这样在同一个页面上我们可以使用多个同样类型的对象。
Jsplet中另外一个扩展是通过事件路由来支持jsp子页面的对象化。例如
http://my.com/demo_main.jsp?objectName=/@Main&eventTarget=/@Sub&objectEvent=test
如果指定了eventTarget参数,则objectEvent由eventTarget对应的对象来响应。
在jsp文件内部我们可以通过include语法来引入子对象,例如
<jsp:include page="sub_view.jsp?objectName=/@Sub" />
(注:我不是非常清楚Tapestry具体是如何实现对象化的,熟悉Tapestry的朋友可以介绍一下)
在Jsplet中可以通过配置文件来支持对Action的interception, 例如
[code]
<factory>
<listener-filter class="global.LogFilter" />
<post-listener class="global.CommonActions"/>
<type name="Demo">
<!-- 如果未指定object, 则缺省为WebObject类型 -->
<object class="demo.MyWebObject" />
<listener>
<filter event="query*|select*" class="demo.LogFilter" />
<url-listener url="/demo/DemoAction.jsp" />
<url-listener url="/demo/DemoAction2.jsp" />
</listener>
</type>
</factory>
[/code]
在上面这个配置文件中,DemoAction.jsp和DemoAction2.jsp是chain关系,即事件响应的传播模型中,如果event没有被标记为stopPropagation,就会传递到下一个listener。
综上所述,可以看到在目前多变的需求环境下,Model 2已不是一种非常完善的Web程序模式,一些重要的设计需求在Model 2模式的推方式中很难得到合适的表达。