所謂WebMVC即Model2模型是目前Web開(kāi)發(fā)領(lǐng)域的主流模型,Struts/Struts2框架是其典型實(shí)現(xiàn)。在概念層面上,這種程序組織模型是怎樣建立起來(lái)的?與其他Web開(kāi)發(fā)模型(如面向?qū)ο竽P?具有怎樣的聯(lián)系? 它未來(lái)可能的發(fā)展方向在哪里? 結(jié)合Witrix開(kāi)發(fā)平臺(tái)的具體實(shí)踐,基于級(jí)列設(shè)計(jì)理論我們可以看到一條概念發(fā)展的脈絡(luò)。
http://canonical.javaeye.com/blog/33824

1. 外部視角:原始的servlet規(guī)范提供了一個(gè)簡(jiǎn)單的面向IO的程序響應(yīng)模型。一次前臺(tái)訪問(wèn)由一個(gè)特定的servlet負(fù)責(zé)響應(yīng),它從request中讀取輸入流,在全局session中保持臨時(shí)狀態(tài),向response中寫入輸出流。在此基礎(chǔ)上,JSP提供的模板概念翻轉(zhuǎn)了程序和輸出文本之間的相對(duì)地位,簡(jiǎn)化了文本輸出過(guò)程。至此,這種整體的程序模型基本上只是規(guī)范化了外部系統(tǒng)訪問(wèn)Web服務(wù)器的響應(yīng)模型,并沒(méi)有對(duì)后臺(tái)程序的具體實(shí)現(xiàn)制定明確的約束條件。因此在最粗野的后臺(tái)實(shí)現(xiàn)中,讀取參數(shù),業(yè)務(wù)處理,生成頁(yè)面等處理步驟是糾纏在一起的,很難理解,也很難重用。每一個(gè)后臺(tái)頁(yè)面都是一個(gè)不可分析的整體。
<%
String paramA = request.getParameter("paramA");
ResultSet rsA = 
%>
result = <%=rsA.getString(0) %>
String paramB = request.getParamter("paramB");
ResultSet rsB = 
<%
rsB.close();
rsA.close();
conn.close();
%>
2. 自發(fā)分離:在復(fù)雜的程序?qū)嵺`中,我們會(huì)自發(fā)的對(duì)業(yè)務(wù)處理代碼和界面代碼進(jìn)行一定程度的分離。因?yàn)槲覀兛梢灾庇^的感受到這兩種代碼的穩(wěn)定性并不匹配。例如不同業(yè)務(wù)處理過(guò)程產(chǎn)生的結(jié)果都可以用一個(gè)html表格來(lái)展現(xiàn),而同一個(gè)業(yè)務(wù)處理過(guò)程產(chǎn)生的結(jié)果頁(yè)面可能經(jīng)常發(fā)生變化。一般我們傾向于將業(yè)務(wù)代碼寫在頁(yè)面上方,而界面代碼寫在頁(yè)面下方,并使用一些原始的分解機(jī)制,例如include指令。這種分離是隨意的,缺乏形式邊界的。例如我們無(wú)法表達(dá)被包含的頁(yè)面需要哪些參數(shù),也難以避免全局變量名沖突。需要注意的是,分層的一般意義在于各個(gè)層面可以獨(dú)立發(fā)展,它的隱含假定是各層面之間的交互是規(guī)范化的,只使用確定的數(shù)據(jù)結(jié)構(gòu),按照確定的方式進(jìn)行交互。例如業(yè)務(wù)層和界面層通過(guò)標(biāo)準(zhǔn)的List/Map等數(shù)據(jù)結(jié)構(gòu)交互,而不是使用具有無(wú)限多種樣式的特殊的數(shù)據(jù)結(jié)構(gòu)。(在弱類型語(yǔ)言環(huán)境中,實(shí)體對(duì)象的結(jié)構(gòu)和Map是等價(jià)的).
<%
List header = 
List dataList = 
%>
<%@ include file="/show_table.jsp" %>
3. 規(guī)范分離:JSP所提供的useBean和tag機(jī)制,即所謂的Model1模型,是對(duì)程序結(jié)構(gòu)分離的一種規(guī)范化。業(yè)務(wù)代碼封裝在java類中,一般業(yè)務(wù)函數(shù)與web環(huán)境無(wú)關(guān),即不使用request和response對(duì)象, 允許單元測(cè)試。tag機(jī)制可以看作是對(duì)include指令的增強(qiáng),是一種代碼重用機(jī)制。tld描述明確了調(diào)用tag時(shí)的約束關(guān)系。調(diào)用tag時(shí)需要就地指定調(diào)用參數(shù),而include頁(yè)面所依賴的參數(shù)可能是在此前任意地方指定的,是與功能實(shí)現(xiàn)分離的。此外tag所使用的參數(shù)名是局部對(duì)象上的屬性名,從而避免了對(duì)全局變量的依賴。很遺憾的是,jsp tag所封裝的仍然是原始的IO模型,對(duì)程序結(jié)構(gòu)缺乏精細(xì)的定義,在概念層面上只是對(duì)文本片段的再加工,難以支撐復(fù)雜的控件結(jié)構(gòu)。早期jsp tag無(wú)法利用jsp模板本身來(lái)構(gòu)造,無(wú)法構(gòu)成一個(gè)層層遞進(jìn)的概念抽象機(jī)制,更是讓這種孱弱的重用模型雪上加霜。在其位卻無(wú)能謀其政,這直接造成了整個(gè)j2ee前臺(tái)界面抽象層的概念缺失,以致很多人認(rèn)為一種前臺(tái)模板重用機(jī)制是無(wú)用的。在Witrix平臺(tái)中所定義的tpl模板語(yǔ)言,充分利用了xml的結(jié)構(gòu)特點(diǎn),結(jié)合編譯期變換技術(shù),成為Witrix平臺(tái)中進(jìn)行結(jié)構(gòu)抽象的基本手段。實(shí)際上,xml能夠有效表達(dá)的語(yǔ)義比一般人所想象的要多得多。
<jsp:useBean id="myBiz" class="
" />
<% List dataList = myBiz.process(paramA) %>
<ui:Table data="<%= dataList %>" />
4. 框架分離:在Model1模型中,頁(yè)面中存在著大量的粘結(jié)性代碼,它們負(fù)責(zé)解析前臺(tái)參數(shù),進(jìn)行類型轉(zhuǎn)換和數(shù)據(jù)校驗(yàn),定位特定的業(yè)務(wù)處理類,設(shè)置返回結(jié)果,控制頁(yè)面跳轉(zhuǎn)等。一種自然的想法是定義一個(gè)全局的程序框架,它根據(jù)集中的配置文件完成所有的粘結(jié)性操作。這也就是所謂面向action的WebMVC模型。這一模型實(shí)現(xiàn)了服務(wù)器端業(yè)務(wù)層和界面層在實(shí)現(xiàn)上的分離,但是對(duì)于外部訪問(wèn)者而言,它所暴露的仍然是原始的自動(dòng)機(jī)模型:整個(gè)網(wǎng)站是一個(gè)龐大的自動(dòng)機(jī),每次訪問(wèn)都觸發(fā)一個(gè)action,在action中可能更改自動(dòng)機(jī)的狀態(tài)(作為全局狀態(tài)容器的session對(duì)象或者數(shù)據(jù)庫(kù))。struts作為面向action框架的先驅(qū),它也很自然的成為了先烈。struts中所引入的FormBean, 鏈接管理等概念已經(jīng)在實(shí)踐中被證明是無(wú)益的。一些新興的框架開(kāi)始回歸到通用的Map結(jié)構(gòu),直接指定跳轉(zhuǎn)頁(yè)面,或者利用CoC(Convention Over Configuration)缺省映射.
public class RegisterAction extends Action {
public ActionForward perform (ActionMapping mapping,
ActionForm form,
HttpServletRequest req,
HttpServletResponse res)
{
RegisterForm rf = (RegisterForm) form;

return mapping.findForward("success");
}
5. 橫向延展:分層之后必然導(dǎo)向各個(gè)層面的獨(dú)立發(fā)展,我們的視野自然也會(huì)擴(kuò)大到單個(gè)頁(yè)面之外,看到一個(gè)層面上更多元素之間的相互作用.在面向?qū)ο笳Z(yǔ)言大行其道的今天,繼承(inheritance)無(wú)疑是多數(shù)人首先想到的程序結(jié)構(gòu)組織手段.后臺(tái)action可以很自然的利用java語(yǔ)言自身的繼承機(jī)制,配置文件中也可以定義類似的extends或者parent屬性.但是對(duì)于前臺(tái)頁(yè)面一般卻很少有適用的抽象手段,于是便有人致力于前臺(tái)頁(yè)面的對(duì)象語(yǔ)言化:首先將前臺(tái)頁(yè)面采用某種對(duì)象語(yǔ)言表達(dá),然后再利用對(duì)象語(yǔ)言內(nèi)置的結(jié)構(gòu)抽象機(jī)制.放棄界面的可描述性,將其轉(zhuǎn)化為某種活動(dòng)對(duì)象,在我看來(lái)是一種錯(cuò)誤的方向.而JSF(JavaServerFace)規(guī)范卻似乎想在這個(gè)方向上越走越遠(yuǎn).JSF早期設(shè)計(jì)中存在的一個(gè)嚴(yán)重問(wèn)題是延續(xù)了面向?qū)ο笳Z(yǔ)言中的狀態(tài)與行為綁定的組織方式.這造成每次訪問(wèn)后臺(tái)頁(yè)面都要重建整個(gè)Component Tree, 無(wú)法實(shí)現(xiàn)頁(yè)面結(jié)構(gòu)的有效緩存.而Witrix平臺(tái)中的tpl模板語(yǔ)言編譯出的結(jié)構(gòu)是無(wú)狀態(tài)的,可以在多個(gè)用戶之間重用.
6. 相關(guān)聚合:對(duì)象化首先意味著相關(guān)性的局域化,它并不等價(jià)于對(duì)象語(yǔ)言化. 當(dāng)面對(duì)一個(gè)大的集合的時(shí)候,最自然的管理手段便是分組聚合:緊密相關(guān)的元素被分配到同一分組,相關(guān)性被局域化到組內(nèi).例如,針對(duì)某個(gè)業(yè)務(wù)對(duì)象的增刪改查操作可以看作屬于同一分組. struts中的一個(gè)最佳實(shí)踐是使用DispatchAction, 它根據(jù)一個(gè)額外的參數(shù)將調(diào)用請(qǐng)求映射到Action對(duì)象的子函數(shù)上.例如/book.do?dispatchMethod=add. 從外部看來(lái),這種訪問(wèn)方式已經(jīng)超越了原始的servlet響應(yīng)模型,看起來(lái)頗有一些面向?qū)ο蟮臉幼樱矁H僅局限于樣子而已.DispatchAction在struts框架中無(wú)疑只是一種權(quán)宜之計(jì),它與form, navigation等都是不協(xié)調(diào)的,而且多個(gè)子函數(shù)之間并不共享任何狀態(tài)變量(即不發(fā)生內(nèi)部的相互作用),并不是真正對(duì)象化的組織方式.按照結(jié)構(gòu)主義的觀點(diǎn),整體大于部分之和.當(dāng)一組函數(shù)聚集在一起的時(shí)候,它們所催生的一個(gè)概念便是整體的表征:this指針.Witrix平臺(tái)中的Jsplet框架是一個(gè)面向?qū)ο蟮腤eb框架,其中同屬于一個(gè)對(duì)象的多個(gè)Action響應(yīng)函數(shù)之間可以共享局部的狀態(tài)變量(thisObj),而不僅僅是通過(guò)全局的session對(duì)象來(lái)發(fā)生無(wú)差別的全局關(guān)聯(lián).
http://canonical.javaeye.com/blog/33873 需要注意的是,thisObj不僅僅聚集了后臺(tái)的業(yè)務(wù)操作,它同時(shí)定義了前后臺(tái)之間的一個(gè)標(biāo)準(zhǔn)狀態(tài)共享機(jī)制,實(shí)現(xiàn)了前后臺(tái)之間的聚合.而前臺(tái)的add.jsp, view.jsp等頁(yè)面也因此通過(guò)thisObj產(chǎn)生了狀態(tài)關(guān)聯(lián),構(gòu)成頁(yè)面分組.為了更加明確的支持前臺(tái)頁(yè)面分組的概念,Witrix平臺(tái)提供了其他一些輔助關(guān)聯(lián)手段.例如標(biāo)準(zhǔn)頁(yè)面中的按鈕操作都集中在std.js中的stdPage對(duì)象上,因此只需要一條語(yǔ)句stdPage.mixin(DocflowOps);即可為docflow定制多個(gè)頁(yè)面上的眾多相關(guān)按鈕操作.此外Witrix平臺(tái)中定義了標(biāo)準(zhǔn)的url構(gòu)建手段,它確保在多個(gè)頁(yè)面間跳轉(zhuǎn)的時(shí)候,所有以$字符為前綴的參數(shù)將被自動(dòng)攜帶.從概念上說(shuō)這是一種類似于cookie,但卻更加靈活,更加面向應(yīng)用的狀態(tài)保持機(jī)制.
class DaoWebAction extends WebContext{
IEntityDao entityDao;
String metaName;
public Object actQuery(){

thisObj.put("pager",pager);
return success();
}
public Object actExport(){
Pager pager = (Pager)thisObj.get("pager");

return success();
}
}
7. 描述分離:當(dāng)明確定義了Action所聚集而成的對(duì)象結(jié)構(gòu)之后,我們?cè)俅位氐絾?wèn)題的原點(diǎn):如何簡(jiǎn)化程序基元(對(duì)象)的構(gòu)建?繼承始終是一種可行的手段,但是它要求信息的組織結(jié)構(gòu)是遞進(jìn)式的,而很多時(shí)候我們實(shí)際希望的組織方式只是簡(jiǎn)單的加和。通過(guò)明確定義的meta(元數(shù)據(jù)),從對(duì)象中分離出部分描述信息,在實(shí)踐中被證明是一種有效的手段。同樣的后臺(tái)事件響應(yīng)對(duì)象(ActionObject),同樣的前臺(tái)界面顯示代碼(PageGroup),配合不同的Meta,可以產(chǎn)生完全不同的行為結(jié)果, 表達(dá)不同的業(yè)務(wù)需求。
http://canonical.javaeye.com/blog/114066 從概念上說(shuō),這可以看作是一種模板化過(guò)程或者是一種復(fù)雜的策略模式 ProductWebObject = DaoWebObject<ProductMeta>。當(dāng)然限于技術(shù)實(shí)現(xiàn)的原因,在一般框架實(shí)現(xiàn)中,meta并不是通過(guò)泛型技術(shù)引入到Web對(duì)象中的。目前常見(jiàn)的開(kāi)發(fā)實(shí)踐中,經(jīng)常可以看見(jiàn)類似BaseAction<T>, BaseManager<T>的基類,它們多半僅僅是為了自動(dòng)實(shí)現(xiàn)類型檢查。如果結(jié)合Annotation技術(shù),則可以超越類型填充,部分達(dá)到Meta組合的效果。使用meta的另外一個(gè)副作用在于,meta提供了各個(gè)層面之間新的信息傳遞手段,它可以維系多個(gè)層面之間的共變(covariant)。例如在使用meta的情況下,后臺(tái)代碼調(diào)用requestVars(dsMeta.getUpdatableFields())得到提交參數(shù),前臺(tái)頁(yè)面調(diào)用forEach dsMeta.getViewableFields()來(lái)生成界面. 則新增一個(gè)字段的時(shí)候,只需要在meta中修改一處,前后臺(tái)即可實(shí)現(xiàn)同步更新,自動(dòng)維持前后臺(tái)概念的一致性。有趣的是,前后臺(tái)在分離之后它們之間的關(guān)聯(lián)變得更加豐富。
8. 切面分離: Meta一般用于引入外部的描述信息,很少直接改變對(duì)象的行為結(jié)構(gòu)。AOP(Aspect Oriented Programming)概念的出現(xiàn)為程序結(jié)構(gòu)的組織提供了新的技術(shù)手段。AOP可以看作是程序結(jié)構(gòu)空間中定位技術(shù)和組裝技術(shù)的結(jié)合,它比繼承機(jī)制和模板機(jī)制更加靈活,也更加強(qiáng)大。
http://canonical.javaeye.com/blog/34941 Witrix平臺(tái)中通過(guò)類似AOP的BizFlow技術(shù)實(shí)現(xiàn)對(duì)DaoWebAction和前臺(tái)界面的行為擴(kuò)展,它可以在不擴(kuò)展DaoWebAction類的情況下,增加/修正/減少web事件響應(yīng)函數(shù),增加/修正/減少前臺(tái)界面展現(xiàn)元素。當(dāng)前臺(tái)發(fā)送的$bizId參數(shù)不同的時(shí)候,應(yīng)用到WebObject上的行為切片也不同,從而可以自然的支持同一業(yè)務(wù)對(duì)象具有多個(gè)不同應(yīng)用場(chǎng)景的情況(例如審核和擬制)。在BizFlow中定義了明確的實(shí)體化過(guò)程,前臺(tái)提交的集合操作將被分解為針對(duì)單個(gè)實(shí)體的操作。例如前臺(tái)提交objectEvent=Remove&id=1&id=2,將會(huì)調(diào)用兩次<action id="Remove-default">操作。注意到AOP定位技術(shù)首先要求的就是良好的坐標(biāo)定義, 實(shí)體化明確定義了實(shí)體操作邊界,為實(shí)體相關(guān)切點(diǎn)的構(gòu)造奠定了基礎(chǔ)。
http://canonical.javaeye.com/blog/33784
9. 背景消除:在Witrix平臺(tái)中, (DaoWebAction + StdPageGroup + Meta + BizFlow)構(gòu)成完整的程序模型,因此一般情況下并不需要繼承DaoWebAction類,也不需要增加新的前臺(tái)頁(yè)面文件,而只需要在BizFlow文件中對(duì)修正部分進(jìn)行描述即可。在某種程度上DaoWebAction+StdPageGroup所提供的CRUD(CreateReadUpdateDelete)模型成為了默認(rèn)的背景知識(shí)。如果背景信息極少泄漏,則我們可以在較高抽象層次上進(jìn)行工作,而不再理會(huì)原始的構(gòu)造機(jī)制。例如在深度集成hibernate的情況下,很少會(huì)有必須使用SQL語(yǔ)句的需求。BizFlow是對(duì)實(shí)體相關(guān)的所有業(yè)務(wù)操作和所有頁(yè)面展現(xiàn)的集中描述,在考慮到背景知識(shí)的情況下,它定義了一個(gè)完整的自給自足的程序模型。當(dāng)我們的建模視角轉(zhuǎn)移到BizFlow模型上時(shí),可以發(fā)展出新的程序構(gòu)造手段。例如BizFlow之間可以定義類似繼承機(jī)制的extends算子,可以定義實(shí)體狀態(tài)驅(qū)動(dòng)的有限自動(dòng)機(jī),可以定義不同實(shí)體之間的鉤稽關(guān)系(實(shí)體A發(fā)生變化的時(shí)候自動(dòng)更新實(shí)體B上的相關(guān)屬性),也可以定義對(duì)Workflow的自然嵌入機(jī)制。從表面上看,BizFlow似乎回歸到了前后臺(tái)大雜燴的最初場(chǎng)景(甚至更加嚴(yán)重,它同時(shí)描述了多個(gè)相關(guān)頁(yè)面和多個(gè)相關(guān)操作),但是在分分合合的模型建立過(guò)程中,大量信息被分解到背景模型中,同時(shí)發(fā)展了各種高級(jí)結(jié)構(gòu)抽象機(jī)制, 確保了我們注意力的關(guān)注點(diǎn)始終是有限的變化部分。而緊致的描述提高了信息密度,簡(jiǎn)化了程序構(gòu)造過(guò)程。
http://canonical.javaeye.com/blog/126467
<bizflow extends="docflow"> <!-- 引入docflow模型,包括一系列界面修正和后臺(tái)操作 -->
<biz id="my">
<tpls>
<tpl id="initTpl">
<script src="my_ops.js" ></script>
<script>
stdPage.mixin(MyOps); // 引入多個(gè)頁(yè)面上相關(guān)按鈕對(duì)應(yīng)的操作
</script>
</tpl>
</tpls>
</biz>
</bizflow>