在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模式的推方式中很難得到合適的表達。