1.背景、形勢
能夠進行Web開發(fā)的編程語言和技術很多
(1) 動態(tài)解釋語言
PHP; Perl; Python (Zope, Plone); Ruby (Ruby on Rails);
(2) 編譯語言
Java; .net
Java Web開發(fā)遠非一枝獨秀:
除了受到來自.net 這個重量級對手的最大挑戰(zhàn)之外,更受到Zope, Ruby on Rail 等新式輕騎兵的沖擊(當然,也繼續(xù)受到老式輕步兵PHP, Perl的沖擊)。
官方Java走的是復雜路線,Servlet -> JSP -> Taglib。.net走的也是復雜路線,依靠成熟友好的集成化開發(fā)環(huán)境取勝。Java陣營好容易應對過來,從紛紜復雜的各種開發(fā)框架基礎上,發(fā)展出了重量級Web開發(fā)框架JSF,以及相應的集成化開發(fā)環(huán)境;渴望以此應對.net的攻勢。勝負未分,前途未卜。這時,另一個方向又殺來了新式輕騎Zope, Ruby on Rail。
Python, Ruby等動態(tài)解釋語言,面向?qū)ο筇匦愿茫忍熘С?動態(tài)綁定、AOP、函數(shù)式編程、“編程即配置”等時髦概念。開發(fā)速度更快,代碼量更小,達到killer級別。
傳統(tǒng)的HTML Web開發(fā)領域里面,Java已經(jīng)是腹背受敵。領域外也展開了征戰(zhàn),Rich Client Architecture的興起:AJAX(XMLHttp), Flash RIA, XUL, XAML, Smart Client(以及從前的ActiveX, Applet, Web Start)。
Web的發(fā)展趨勢是 語義Web,最終目的是讓整個Web成為一個巨大的數(shù)據(jù)庫。
這意味著,未來的Web應用將更加的面向文本內(nèi)容數(shù)據(jù),更加搜索引擎友好 – Search Engine Friendly.
二進制的客戶端插件,如Flash RIA, ActiveX, Applet, Web Start等,雖然交互性能最好,但不是以文本內(nèi)容數(shù)據(jù)為中心,搜索引擎不友好。所以,我只是保持適當關注。我更關注基于文本的UI表現(xiàn),如HTML, XUL, XAML等。XUL, XAML還沒有廣泛流行,只是保持一種有興趣的關注。
當下關注的重點,還是 XHTML + CSS + Javascript少量的 AJAX(XMLHttp)增加更好的交互性。
我一直認為:輕量、簡潔、高效 才是硬道理。后面闡述我對Java Web開發(fā)的理解和構(gòu)想。
2. Web開發(fā)框架層次概述
從上到下,Web開發(fā)框架的層次如下:
(1) HTML, JavaScript, CSS等頁面資源。
(2) 頁面模板層。 如JSP, Freemarker, Velocity, XSL,fastm等。用來生成HTML, JavaScript, CSS等頁面資源。
(3) Web框架。把HTTP Request調(diào)度分派到對應的Service Entry。
(4) Business Logic.
(5) O/R Mapping.
(6) JDBC
(7) DB
根據(jù)我的經(jīng)驗,一個典型的Web應用中的代碼比例如下:
頁面邏輯約占 50%,商業(yè)邏輯約占30%, O/R 約占20%。
但事實上,頁面卻是最不受重視的部分,從來都被認為是臟活,累活,雜活。典型的開發(fā)過程通常是這樣:
頁面設計人員迅速的用Dreamweaver等生成一堆文本雜亂無章的頁面,然后交給JSP程序員加入更加雜亂無章的Java代碼和Taglib。
當頁面布局風格需要改變的時候,頁面設計人員用Dreamweaver等生成一堆新的頁面。JSP程序員再重新加入更加雜亂無章的Java代碼Taglib。
至于頁面中的腳本邏輯調(diào)試,更是一門精深的工夫了。
根據(jù)社會規(guī)則,通常來說,工作內(nèi)容越輕松,收入越高;工作內(nèi)容越臟月累,收入越低;Web開發(fā)也是如此:做著最臟最累的活的頁面程序員,工資一般比不上后臺業(yè)務邏輯程序員。
開發(fā)框架通常會帶來這樣的結(jié)果:讓簡單的東西,變得更簡單;讓復雜的東西,變得更復雜。
這其中的原因在于:
一般來說,一個應用中簡單重復的東西占80%,復雜特殊的東西占20%。
簡單重復的東西很容易摸清規(guī)律,進行包裝,通用化。但是,在包裝的同時,經(jīng)常就阻擋住了底層的一些靈活強大的控制能力。在復雜特殊的需求中,確實又需要這些底層控制能力,那么為了繞開框架的限制,付出的努力要比不用框架 大得多。
打個比方,一個比較極端的例子。編譯語言比匯編語言的開發(fā)效率高很多,但是卻無法直接操作寄存器。當需要在編譯語言中操作寄存器的時候,就非常的痛苦。比如Java,也許需要JNI,寫C代碼,還要在C代碼里面嵌入?yún)R編。編譯、連接都很麻煩。
所以,一個框架的開發(fā)效率,就在于這個80%簡單 與 20%復雜之間的平衡。
假如,不用框架來開發(fā),簡單的80%要消耗 80個資源數(shù),復雜的20%要消耗20個資源數(shù),總資源數(shù)是100;使用了某個框架,簡單的80%只要消耗10個資源數(shù),復雜的20%要消耗40個資源數(shù),總資源數(shù)是50。那么,我們說,這個開發(fā)框架是有效率的。
我的思路是,同時應對復雜和簡單。當然,為了應對復雜,簡單的東西可能就應對得不那么好。比如,做這樣一個開發(fā)框架,簡單的80%要消耗20個資源數(shù),復雜的20%要消耗10個資源數(shù),總資源數(shù)是30。
這種開發(fā)框架是有可能實現(xiàn)的。而且是很有意義的。尤其是在復雜部分的比例提高的時候。越復雜的系統(tǒng),這種開發(fā)框架就越有意義。
后面的關于Web各層開發(fā)的論述,主要就按照這個“應對復雜、讓復雜更簡單”的思路展開。
3.頁面資源
也許有人會說,頁面資源,不就是HTML嗎?太簡單,太低極了,沒勁。Dreamweaver、Frontpage多簡單阿。隨便找個人來用就可以了。文本內(nèi)容亂糟糟不要緊,瀏覽器里面顯示出來的效果好看就行。要增加炫的、酷的動畫效果,那就寫JavaScript唄。寫在HTML里面,看看在IE里面能不能運行就可以了唄。
這也正是大多數(shù)公司開發(fā)頁面資源的方式。因為頁面的需求變化是最多、最快的,而頁面的制作成本很低,人們不愿意在上面投入更多的資源。
我的看法是,萬丈高樓平地起。應用程序的每一個部分都應該完善管理,結(jié)構(gòu)優(yōu)美。越是需求變化多的地方,越是臟亂差的地方,越應該加大力度處理好。
頁面結(jié)構(gòu)方面,Javaeye論壇的Dlee做了很多工作。
(1) 在 2005 年我們?nèi)绾螌?JavaScript
http://forum.javaeye.com/viewtopic.php?t=12973
(2)使用 Unordered Lists 制作的下拉菜單和樹
http://forum.javaeye.com/viewtopic.php?t=12995
從上面的Dlee的論述和給出的資料。可以看出,頁面資源分為三部分:
(1) XHTML。結(jié)構(gòu),Structure。
XHTML里面的Tag部分只應該包括 <ul> <table> <p> <div><span>等結(jié)構(gòu)布局Tag,或者<strong><emphasis>表示語義的Tag。
XHTML里面不應該包括風格信息,比如字體、顏色、大小、粗細等,也不應該包括<font> <b> <i> <h> 等字體信息。
XHTML里面不應該包括Javascript的定義和調(diào)用。
(2) JavaScript。行為,behavior。
JavaScritp應該存在于一個獨立于XHTML文件的獨立文件中。這樣可以做自動化單元測試。JavaScript應該只改變HTML DOM的結(jié)構(gòu)和內(nèi)容,而不應該改變它的風格。
(3) CSS。Style,風格。或者說,Presentation,表現(xiàn)。
前面說了,XHTML里面不應該包括JavaScript的調(diào)用。那么,XHTML的元素是如何JavaScript事件綁定起來?就是在CSS里面指定的。
當然,眾所周知,CSS的本職工作是處理頁面風格。
頁面資源方面,我完全認同Dlee的觀點。從技術和資源積累的長遠目標看來,這方面的初期投入的回報將是非常豐厚的。
即使將來HTML消亡了,進入了XAML, XUL, RSS時代,這些結(jié)構(gòu)清晰的各部分,重用的可能性都非常巨大。JavaScript + CSS + XML UI的這種經(jīng)典設計思路,將留存很久。混雜成一團的HTML的命運只能是全盤被拋棄。
4.頁面模板層
頁面模板層是指Server端運行的用來生成HTML(或JavaScript,CSS)的Server Side Template Engine。
這一層也是著名的臟亂差樓層。著名的HTML的Java代碼污染事件,就發(fā)生在這個樓層。不僅JSP有這個問題,其他的template, 如freemarker, velocity, tapestry等含有邏輯的腳本,都不同程度上有HTML的Script Logic污染問題。
Dlee的做法很美。直接就不要頁面模板層,不用Server Side Template Engine。直接用JavaScript更改HTML DOM的結(jié)構(gòu)、內(nèi)容、數(shù)據(jù)。同時,會用到少量的瀏覽器端XSL。
這樣帶來的結(jié)果,Template就是很干凈純粹的HTML,不含有任何Server Side Script。這個效果,和Servier Side Template 的 Jivan,XMLC達到的一樣。只是一個是在瀏覽器端執(zhí)行,一個是在Server端執(zhí)行。
我研究比較了幾乎所有的Server Side Template Engine,力圖采眾家之長,避眾家之短,寫了一個Server Side Template Engine -- fastm, 能夠最優(yōu)雅方便的實現(xiàn)頁面模板層。關于fastm,我的Blog上有不少文章論述。
我的Blog,里面專門有個fastm 分類。
http://blog.csdn.net/buaawhl
http://buaawhl.blogdriver.com
Fastm發(fā)布在java.net上。
https://fastm.dev.java.net
我仍然對Server Side Template Engine持肯定態(tài)度。基于如下原因:
(1) JavaScript代碼量大、文件多的時候,不容易管理,不容易進行語法檢查,不容易跟蹤調(diào)試。
這里有人會爭辯,Server Side Template Engine也用到了很多腳本阿,比如Freemarker, Velocity, 而且嵌在HTML中,怎么管理,怎么調(diào)試?即使是JSP,也是Java Code嵌在HTML里面,怎么管理,怎么調(diào)試?
這里我要說,Jivan, XMLC, fastm,Wicket等Template Engine的邏輯都是在Java Code里面。
(2) 用JavaScript生成文本內(nèi)容,搜索引擎不友好。
一般的網(wǎng)絡蜘蛛程序,只根據(jù)URL獲取HTML文本,搜索里面的文本內(nèi)容,而不會執(zhí)行里面的JavaScript腳本。
(3) JavaScript代碼重用還是有些局限
比如,有兩個HTML文件,一個是Table布局,一個是List布局。
我有同樣的一批數(shù)據(jù),要在這兩種布局中顯示。
這時候,就要給這兩個HTML分別寫兩套JavaScript。這里面的DOM層次,元素,屬性都不同,再怎么定義ID,Class,也無法用完全相同的一套JavaScript處理。
這里有人會爭辯,Server Side Template Engine也無法做到。別說JSP, Velocity, Freemarker等要在兩套HTML里面嵌入相同的代碼,就是Jivan, XMLC, Wicket也要分別寫不同的兩套Java Code,因為它們的XML DOM Node / Model View (Table, List) 都是不同的。
這里我要說。fastm可以做到只用一套代碼邏輯。而且只有fastm可以。fastm的代碼重用率是最高的。
關于Ajax(XMLHttp),我的意見是必要時才用,而且最好采用粗粒度的用法 -- JavaScript發(fā)出一個URL請求,返回一整段HTML,直接替換到頁面的某一塊,而不是用JavaScript來做這樣的把數(shù)據(jù)填充到HTML DOM中。如果你直接在瀏覽器里面輸入那個URL,也可以獲取那整段的HTML內(nèi)容。
典型的應用場合是Portal。Portal頁面的每個Portlet都含有這樣的 Ajax(XMLHttp) javascript代碼 -- 發(fā)出一個Portlet URL請求,返回一整段Portlet的內(nèi)容,直接替換當前的Portlet塊。
這樣做的好處是:
(1) 減少JavaScript代碼的量和復雜度。
(2) 搜索引擎友好。網(wǎng)絡蜘蛛程序可以辨別JavaScript中的URL,并根據(jù)這個URL,獲取整段處理好的HTML文本,進行內(nèi)容搜索。
有人可能會爭辯:如果URL請求返回的是XML數(shù)據(jù),不是整段處理好的HTML,搜索引擎也可以進行內(nèi)容搜索。
這點我同意。前提是XML數(shù)據(jù)的內(nèi)容是足夠連貫的,而不是散落的。比如,你返回的XML數(shù)據(jù)是“中國”。這個“中國”要放在HTML中的一個{country}位置,{country}足球。這個時候,結(jié)果HTML的內(nèi)容含有“中國足球”。而XML數(shù)據(jù)中只含有“中國”。如果用戶用“中國足球”作為關鍵字來搜索,就找不到這個URL。
從前面給出的fastm資料的連接中,可以得知。如同Jivan, XMLC, Wicket一樣,fastm的template里面不含有邏輯,所有的邏輯都寫在Java里面。
有人會爭辯說:頁面邏輯寫在Java里面,我改變了頁面邏輯,還需要重新編譯。這也太不方便了。Velocity, Freemarker, JSP就不用重新編譯。
這里我的看法是:業(yè)務邏輯代碼改變了,不也需要重新編譯嗎?頁面邏輯就不是邏輯了嗎?HTML里面的腳本怎么語法檢查、跟蹤調(diào)試?業(yè)務邏輯需要語法檢查、跟蹤調(diào)試,頁面邏輯就不需要語法檢查、跟蹤調(diào)試了嗎?
對方可能會說:在我的應用中,頁面邏輯的改動需求非常頻繁,而且這些頁面邏輯非常簡單,不需要語法檢查、跟蹤調(diào)試。
這里我的意見是:
(1) 那就使用JSP, Velocity, Freemarker等腳本。
(2) fastm, Jivan, XMLC, Wicket的Java代碼部分也可以寫在腳本里面,比如,Server Side JavaScript, Jython(Python), Groovy, Bean Shell 等腳本語言都可以很方便的和Java相互調(diào)用。
fastm的生命周期將很長。
HTML, XUL, XAML都是,或?qū)⑹强梢栽跒g覽器或可視化編輯工具里面顯示的XML UI定義語言。Microsoft Office的Word, Excel, Powerpoint等格式都提供了相應的XML格式。這些XML文件都可以在Office里面顯示,并編輯。
Adobe公司也提供了PDF的XML格式 -- XDP。可以在Adobe Designer里面顯示并編輯。
由于fastm是Designer Friendly的XML UI所見即所得的模板技術。這方面具有很大的潛力。
根本不需要第三方花大力氣專門做個IDE,來顯示自定義的Tag。目標文件格式提供商自己的閱讀編輯工具就可以直接用了,而且效果就是運行后產(chǎn)生的結(jié)果文件的效果。
即使沒有可視化要求的場合。比如,Web Service需要的XML數(shù)據(jù)。fastm同樣有用武之地。比如,
<!-- BEGIN DYNAMIC: users -->
<user>
<name>{name}</name>
<address>{name}</address>
</user>
<!-- END DYNAMIC: users -->
可以很容易的把一個Java Object List轉(zhuǎn)化為XML數(shù)據(jù)。
另外,我不得不承認。瀏覽器端的JavaScript的頁面邏輯,可移植性要高于Server Side Template Engine。因為Server Side Template Engine通常是特定語言相關的。
目前fastm是用Java實現(xiàn)的。由于實現(xiàn)很簡單,移植到其它的語言,也很簡單。如果是移植到Python, Ruby等動態(tài)解釋語言,那就更簡單了。我是有這個考慮,因為Zope, Ruby on Rails 的模板還是Logic 和 HTML混雜的,fastm這個思路有很大的用武之地。
前面講了這么多。清理了兩層有名的臟亂差的老大難的樓層 -- 頁面資源層和頁面模板層。讓這兩層變得和下面的樓層同樣的優(yōu)雅、清潔。
下面該講到Web框架層了。在向下講之前,由于前面提到了腳本,我想先插入一段關于“可配置”、“可編程”、“可熱部署”、“腳本邏輯 vs XML Tag邏輯”的話題。把這個人們比較關心、討論比較多的話題,先講清楚。
5.可配置、可編程、可熱部署、腳本邏輯 vs XML Tag邏輯
由于Java是編譯語言,人們通常把變化的參數(shù)部分抽取出來,放到配置文件中。
這些配置文件通常是XML文件。這很好,沒什么問題。XML很適合用來表達數(shù)據(jù)結(jié)構(gòu)。
但是,對于某一種技術的狂熱,通常引起對這種技術的過度使用,或者誤用。
人們開始覺得,XML能夠表達一切東西,包括for, if, else等邏輯。這方面的典型例子有 Workflow XML Definition,Logic TagLib, XSL Logic Tag等。
這點我不敢茍同。我的看法是,XML不適合表達邏輯,XML表達邏輯非常蹩腳。XML表達邏輯相當于自定義一門XML格式的腳本語言。
比如,Logic Tablib,很難自然的支持 if else, switch。只能蹩腳地支持一堆 <logic:if> <logic:ifNot> <logic:exists> <logic:notExists> <logic:ifNull> <logic:notNull>。
(注,好久沒有接觸過Taglib了。這些Tag Name都是憑以前的使用印象寫的,也許名字不對,但表達這些意思的TagLib都還是有的)
如果要表達if () else if() else 就更蹩腳了。要進行非常麻煩的嵌套。
再比如,XSL 支持if, else 也非常蹩腳。非要多出來一個層次才行。
<xsl:choose>
<xsl:when test="…">
…. If ….
</xsl:when>
<xsl:otherwise>
… else …
</xsl:otherwise>
</xsl:choose>
同樣,如果要表達if () else if() else 就更蹩腳了。
<xsl:choose>
<xsl:when test="…">
…. If ….
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="…">
…. If ….
</xsl:when>
<xsl:otherwise>
… else …
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
可以看到,XML Tag 表達邏輯,非常麻煩,可讀性很差,完全是一種誤用,沒有半點優(yōu)勢。當然,邏輯簡單的情況下,還是可以接受的。
有人會說:XML表達邏輯,可以免編譯阿。
那么我說:語法檢查呢,跟蹤調(diào)試呢?
對方說:只是一些簡單的邏輯,不需要語法檢查、跟蹤調(diào)試。
我說:如果只是為了免編譯,前面列出的那么多的解釋執(zhí)行的腳本語言更適合。XML表達的邏輯,比Java等編譯語言還要麻煩很多,而腳本語言比Java等編譯語言簡潔多了,可讀性非常好,而且腳本語言和Java語言有很好的交互性,可以相互調(diào)用。重用、結(jié)構(gòu)方面都具有優(yōu)勢。
有人會舉出Spring IoC為例子,說:你看,Spring IoC的配置文件不都是XML格式嗎?
我說:
(1) Spring IoC的配置文件基本都是屬性設置,Bean ID聲明。沒有邏輯。
(2) 我也不是很贊同Spring IoC在XML配置文件里面引用Java類的做法。這方面,其它的容器如 Pico, Nano都支持多種配置方式,其中包括了不少腳本方式。我覺得,在腳本里面定義生成Java Object,比在XML中要好。當然,Web.xml里面也引用了Java Class名字。但那是非常簡單的情況。沒有嵌套引用、屬性賦值、構(gòu)造參數(shù)等復雜的定義方式。XML適合描述一些通用的資源、數(shù)據(jù)、結(jié)構(gòu)。比如,HTML, XUL, XAML,RSS就是XML用的恰當?shù)睦印?
所以,我的基本觀點是這樣。
(1) 純數(shù)據(jù),不用說,應該定義在XML中。
(2) 如果是系統(tǒng)中一些Java Object要用到的基本屬性。比如,連接池大小等。定義在properties, XML, Script中都可以。如果定義中沒有出現(xiàn)具體的Java Class名,傾向于定義在properties, XML文件中。如果出現(xiàn)了具體的Java Class名,傾向于定義在Script中。這個界限不那么明顯,兩者皆可。
(3) 復雜結(jié)構(gòu)的Java Bean的構(gòu)造生成,那是肯定會出現(xiàn)具體的Java Class名,應該定義在Script中。
關于“可配置 vs 可編程”,有一點要明確:只要是可編程的,一定是可配置的。但如果是可配置的,卻不一定是可編程的。
這里的可編程,是指框架給程序員提供了API;可配置,是指框架給程序員提供了配置文件的格式寫法。
“可編程”一定是“可配置”的。
(1) 用戶至少可以自己定義配置文件,讀取參數(shù),調(diào)用API。
(2) 有那么多的解釋腳本可以直接和Java互操作,完全可以直接用來當作配置文件,定義參數(shù)。
“可配置” 卻不一定“可編程”的。
如果框架只給你提供了配置方式,而沒有API,那意味著,你只能進行參數(shù)的靜態(tài)配置。很難在動態(tài)期間改變這些參數(shù)了。你總不能嘗試著用代碼去改變配置文件的內(nèi)容吧?即使你改動了,如果框架不進行文件的時間戳檢查,就是一開始裝載進來,就不再檢查更改了,你不就一點辦法都沒有了嗎?
比如,Struts Tiles的XML定義,你只能靜態(tài)配置,你想在運行期間改變布局,沒有辦法。Site Mesh也是如此。而我們可以在運行期間任意操作XML DOM Node,別說布局了,任何東西都可以改變。
所以,一個框架首要注重的是提供API,而不是提供配置方式。這是一個重要的原則。
討論完了“可編程”、“可配置”問題,我們來看“熱部署”問題。
XML配置文件、腳本文件支持“熱部署”當然要比編譯語言程序的熱部署容易得多。只要解釋執(zhí)行前,檢查一下時間戳就可以了。要注意的問題,只是做好測試,因為沒有編譯期的語法檢查。
不過,Java程序也是可以“熱部署”的。只是稍微麻煩一點。典型的例子是JSP, EJB Jar等。JSP修改之后,會自動編譯執(zhí)行;EJB Jar丟到EJB Container里面,會被檢測到并裝載到JNDI命名空間。
編譯語言Java程序的熱部署的一個可能的技術難點是,Class或者Jar已經(jīng)存在,如何監(jiān)測到Class或者Jar的更改,并裝載這個新版本,替換舊版本。
這個問題我具體沒有研究過。從道理上講,應該在Class Loader上下功夫。如果需要,可以參閱開源EJB Container的相關實現(xiàn)部分。Java還有一種“Hot Swap”技術,專門解決這個問題,可以搜索查閱一下。
這段小插曲,就到這里。下面討論Web框架。
6.Web框架
Web框架層是一個清潔的樓層。很多優(yōu)秀的程序員在這一層大展身手,做出了很多好作品。我感覺不錯的有Spring MVC, Web Work。
對于Web應用來說,Web框架層是最重要的一層。SOA、Semantic Web等效果都要在這一層實現(xiàn)。
首先,我們來討論,框架的編程結(jié)構(gòu)。
我的Blog中有一篇《Java Web框架綜述》的文章。講解了一些流行的Web框架的編程結(jié)構(gòu),很多重復的內(nèi)容不再贅述。
http://blog.csdn.net/buaawhl
Java Web框架綜述
http://blog.csdn.net/buaawhl/archive/2004/12/21/224069.aspx
Spring MVC的編程接口是最清晰的。大多數(shù)簡單情況下,Web Work的用法是最簡單有效的,編程結(jié)構(gòu)比較特殊,可以說具有一定的變革意義。
Spring MVC的Controller接口相當于Struts Action,也具有Request, Response兩個參數(shù),雖然編程接口非常清晰優(yōu)雅,但是本質(zhì)上沒有什么變化。
WebWork的Action則失去了Controller的身份,只相當于FormBean的身份,或者說相當于ActionBean的身份。WebWork Action不具有Request, Response兩個參數(shù),它只具有屬性,并通過屬性Setter獲取HTTP Request的參數(shù),通過屬性getter把結(jié)果數(shù)據(jù)輸出到HTTP Response。
可以說,WebWork的這個把握是相當?shù)轿坏摹?5%以上的情況下,程序員是不需要Request, Response參數(shù)的。當需要這些參數(shù)的時候,WebWork并沒有擋住路,可以通過實現(xiàn)RequestAware,ResponseAware等接口來獲取,或者通過一個Thread Local獲取。這種情況下,編程結(jié)構(gòu)的約定,就不那么清晰了。
我從Canonical的帖子和Blog受到了很多啟發(fā)。
http://canonical.blogdriver.com
jsplet:對Model 2模式的批判
http://canonical.blogdriver.com/canonical/591479.html
jsplet與webwork的概念對比
http://canonical.blogdriver.com/canonical/594671.html
從級列理論看MVC架構(gòu)
http://canonical.blogdriver.com/canonical/579747.html
從Canonical的文章可以看出。JSPLet用JSP文件作為Dispatcher,然后在JSP里面注冊并調(diào)用對應的Object。這個尋訪Object的過程,完全是根據(jù)豐富的URL定義來做的。URL里面包括Object Scope, Object Name, Method Name, Method Parameters,天生就對事件機制有良好的支持。
Zope的一些做法也有異曲同工之妙。
Zope Object Publishing
http://www.zope.org/Documentation/Books/ZDG/current/ObjectPublishing.stx
http://www.plope.com/Books/2_7Edition/ZopeArchitecture.stx#2-3
這種通過URL獲取Published Object的服務的思路,是一種實現(xiàn)SOA效果的有效思路。
我們首先來看Web Service的現(xiàn)狀。目前Web Service主要分為兩大陣營。SOAP和REST。關于REST,請參閱
http://www.xfront.com/REST-Web-Services.html
關于SOAP和REST的比較、互操作,網(wǎng)上有很多文章。如果需要請搜索查閱。
我個人比較傾向于REST風格的Web Service。
因為SOAP是一門固定的協(xié)議,如果用SOAP來編寫Web Service程序,需要一個SOAP協(xié)議的解析庫 ,也許還需要一些專門的“SOAP 數(shù)據(jù) -- 編程語言”映射庫,如同CORBA IDL的多語言映射一樣。如果你要讓自己的Web應用支持SOAP,你需要把發(fā)布的服務對象、方法都包裝為SOAP協(xié)議,這需要一些編程語言相關的數(shù)據(jù)結(jié)構(gòu)的映射工作。
REST則只是一種風格,而不是一個協(xié)議。中心思想是簡單的通過豐富的URI定義 (如XLink + XPointer等) 獲取資源。如果你要讓自己的Web應用支持REST,那么很簡單,只要在URI上下功夫就可以了,比如,多增加一個參數(shù)format=REST,在程序中多增加一種XML輸出格式就可以了。(從道理上來說,SOAP也可以這么實現(xiàn),但SOAP的輸入和輸出都要遵守SOAP協(xié)議,SOAP的輸入?yún)?shù)一般都包裝在SOAP信封里面)
關于HTTP Get和Post,我表述一下自己的看法。
我認為,Web的精髓在于Get,而不是Post,在于獲取服務器的輸出,而不是輸入到服務器。即,Web的精髓在于以小搏大,四兩撥千斤。最經(jīng)典的用法就是用一個URL,獲取一個長篇的文本內(nèi)容,這個內(nèi)容里面充滿了其他更多的資源連接。這也是超文本連接HTML發(fā)明的初衷。
至于HTTP Post,則是這上面的一個擴展。B/S結(jié)構(gòu)如此流行,很多應用都要轉(zhuǎn)移到Web上面,怎么辦,應用總是交互的,總要讓用戶輸入數(shù)據(jù)吧,就增加了HTTP Post協(xié)議。
HTTP Get經(jīng)典、簡單、有效。可以用豐富的URI定義把這個優(yōu)勢發(fā)揮到極致。這個實現(xiàn)也比較簡單、優(yōu)雅。就不多說了。主要的難點在于HTTP Post。下面的討論主要應對“HTTP Post”這個復雜現(xiàn)象。
HTTP Post從來就不讓人們滿意。當輸入邏輯復雜到一定程度,表單數(shù)據(jù)的繁雜、凌亂、散落,到了服務器端很難組織起來。輸入方面B/S結(jié)構(gòu)確實和C/S結(jié)構(gòu)難以匹敵。于是,出現(xiàn)了XMLHttp,能夠把參數(shù)在瀏覽器里面組織成為一個統(tǒng)一的XML數(shù)據(jù)結(jié)構(gòu)(或其他格式),發(fā)送到服務器端,一次解析出來。SOAP做這個方面,更是拿手好戲。所以,很多XMLHttp程序直接采用SOAP作為通信協(xié)議。而REST風格的HTTP Post則和HTML Form Post沒有太大的本質(zhì)區(qū)別。
REST在HTTP Get方面更勝一籌,SOAP在HTTP Post方面更勝一籌。可以根據(jù)Web應用的特點,根據(jù)HTTP Get / HTTP Post 頁面的比例,選擇適合的技術。
我們再進一步分析HTTP Post的數(shù)據(jù)內(nèi)容。HTTP Post的數(shù)據(jù),可能包含三種類型:
(1) 需要存檔在服務器的數(shù)據(jù)
比如,用戶注冊時候,輸入的基本信息,用戶名、密碼、電子郵件等。這些信息要存放到服務器的數(shù)據(jù)庫。
對于這種基本信息,HTTP Post,XMLHttp,SOAP處理起來,難度都不大,沒有很大區(qū)別。
B2B的數(shù)據(jù)交換,也屬于這個類別。用何種技術區(qū)別不大。一般采用SOAP,因為SOAP是一種流行的標準協(xié)議。
(2) 服務調(diào)用參數(shù)
比如,用戶進行復合條件查詢的時候,輸入的查詢條件。這個時候,HTTP Post處理起來就非常蹩腳。而XMLHttp,SOAP則具有很大的優(yōu)勢。可以把復雜的查詢條件很好組織成XML數(shù)據(jù),發(fā)送到服務器端統(tǒng)一處理。SOAP里面甚至可以定義對象名、方法名等詳細的調(diào)用信息。
(3) 指令
這種情況比較少見。上面的參數(shù)類別中提到的“對象名、方法名等詳細的調(diào)用信息”,和這個指令類別有些交叉。
假如一個SOAP調(diào)用方法里面的參數(shù)也是一個自定義的對象,這個自定義對象的屬性數(shù)據(jù)在SOAP信息中進行了定義。到了服務器端之后,服務端程序首先調(diào)用這個自定義參數(shù)的構(gòu)造函數(shù),生成這個參數(shù)對象,然后調(diào)用對應的服務對象,把這個參數(shù)傳給服務。這個過程可以看作是一個順序指令:[1]構(gòu)造參數(shù)[2]調(diào)用服務。
這只是最簡單的情況。而目前的Web Service一般也就支持到這個程度。
我的看法是,一不做,而不休。既然都把調(diào)用信息定義到這個程度了,不如做的更徹底一些,全面完善的支持指令。這個指令則意味著邏輯。前面講過了,我不贊成用XML Tag表示邏輯,而贊成腳本。這里比較適合的腳本是JavaScript,因為JavaScript比較通用,客戶端、服務器端都可以解釋執(zhí)行。注意,這里和一般的做法正好相反:一般的Web應用總是把JavaScript從服務器傳到瀏覽器里面執(zhí)行,而這里是把JavaScript在瀏覽器里組織好,發(fā)給服務器端處理;這個JavaScript將會在服務器端執(zhí)行,調(diào)用服務器端的對象。舉個SOAP含有JavaScript指令的例子 (只是示意,非標準格式) :
<soap envelope>
<XML Data>
<a>
<b>12</b>
</a>
<c>
<d>21</d>
</c>
<e>
<e>16</e>
</e>
</XML Data>
<script>
final_result = default;
result1 = service1.service(a.b);
if(result1.ok){
result2 = service2.service(c.d);
if(result2.ok)
final_result = service3.service(e.f);
}
</script>
< /soap envelope >
這個好處是:
[1] 發(fā)布了更多的基本Service。給客戶提供了更大的靈活度。
比如,這里就發(fā)布了3個Service。由用戶自己組織邏輯。
按照傳統(tǒng)的做法,上述流程將整個包裝在服務器端執(zhí)行。發(fā)布給用戶的Service只有最外面的一個Service,而且高度耦合(if, else, if, else流程hard code在服務器端),不靈活,不通用。
這里的方法,就可以讓客戶端隨意組織service1, service2, service3的調(diào)用順序和方式。
[2] 減少了通信次數(shù)。
假如這段Script在客戶端執(zhí)行,那么和服務器要進行3次通信。
傳統(tǒng)Web的權(quán)限控制一般在URL級別,這種script -> server方式的權(quán)限控制則要在對象級別、方法級別、Code片斷級別了,復雜很多,也許要大量應用Java的Code權(quán)限認證機制。
以上展開討論了 Web Service, HTTP Get/Post。下面我們回到Web框架層。
前面說了,JSPLet給了我很大的啟發(fā)。很多思路可以借鑒。
當然,我并不贊成用JSP作Dispatcher, Controller。(1) 因為JSP要編譯成Servlet,而Servlet是Web Server管理的比較昂貴的資源。一個Web系統(tǒng)中JSP達到幾千個,就會遇到性能瓶頸。(2) JSP中的代碼重用很成問題。一般只能通過include file的方式。
可以借鑒的思路。(1) JSPLet 的入口是JSP文件,這一步的URL到處理程序的映射是Servlet/JSP Container自然支持的。這是免配置的。(2) 豐富的URL參數(shù)定義,良好的對象方法尋址能力。
我開發(fā)的開源Web框架lightweb,將具備如下特性:
(1) 支持兩個層次的編程接口。
interface Action { void service(request, response, servletContext); }
這個Action比Struts Action, Spring MVC Controller高一個級別。相當于Dispatcher, 相當于JSPLet的JSP控制文件。這個用來做最外層的入口控制。
同時,也支持簡單的JavaBean.method的直接調(diào)用。相當于WebWork Action,JSPLet Registered Object。這個用來做具體的事情。
(2) 支持豐富的對象尋址URI,比如http://my.com/myProject/myModule/myEntry.action?object=calculator&method=add&p1=1&p2=3
這表示要通過 myEntry.acion這個入口,調(diào)用caculator.add(1, 2)方法。
如果用URL Rewriter可以美化為
http://my.com/myProject/myModule/myEntry/calculator/add/1/3
看起來就很象XLink + XPointer了。
(3) 免配置。或者說極少的配置。
框架根據(jù)一定的匹配準則,把myModule/myEntry.action映射到
com.mycompany.mymodule.MyEntryAction 這個類的service方法。
這個service方法負責根據(jù)object, method的名字,尋找到對應的bean,并根據(jù)參數(shù)進行屬性設置驗證,并執(zhí)行對應的bean.method。然后,把這個bean作為Model和template結(jié)合,輸出結(jié)果。
同樣,template的獲取也是根據(jù)一定的匹配準則,根據(jù)myModule/myEntry找到
Mymodule/myentry.html 或者Mymodule/myentry/calculator.html。
這樣的lightweb就能夠同時對應簡單和復雜。復雜控制的需求交給Action接口來做,簡單的一般具體任務交給普通Java Bean去做。
Web框架層可以做的非常復雜,可以做的非常簡單。Lightweb的目標,就是分成多個簡單的部分;各部分合起來就能夠完成從非常簡單到非常復雜的需求。
接下來,我們來看O/R。
7.O/R
Hibernate, EJB Entity Bean產(chǎn)品,JDO產(chǎn)品,iBatis是比較流行的幾種O/R Mapping Framework。
我做的一些工作中,經(jīng)常涉及到復雜的優(yōu)化過的native SQL,并且涉及到大量的批量復雜邏輯處理,現(xiàn)有的O/R框架都不能滿足功能和性能要求。
我做出這樣一個lightor框架,思路借鑒了Martin Fowler的《企業(yè)架構(gòu)模式》里面講述的一些O/R的Row Mapper, Column Mapper等概念。
最經(jīng)典的用法是:
ResultSet rs = ps.executeQuery( a long complex native sql);
//will return a lot of records
A a = new A();
B b = new B();
IMapper aMapper = MapperService.getMapper(A.class);
IMapper bMapper = MapperService.getMapper(B.class);
While(rs.next()){
aMapper.populate(a, rs);
bMapper.populate(b, rs);
businessLogic(a, b);
}
可以看到,Lightor不需要一下子把所有紀錄都放到一個Object List里面。完全可以隨取隨用。整個過程中,a, b只有一份,極大的節(jié)省了空間、時間,也極大的提高了開發(fā)效率,減少了重復代碼。
沒有任何一個其它O/R能夠支持這種用法。這里面,lightor的mapper的populate方法需要ResultSet參數(shù)。一般的O/R不屑于這么做的,別說ResultSet,連Connection都想包裝起來不給你看。
Lightor的設計思路也是同時應對簡單和復雜。Lightor的Mapper實體部分是自動生成代碼。類似于JDO的靜態(tài)Enhance。不同的是,JDO靜態(tài)Enhance直接修改bean class。而Lightor則不動原有的bean,只是多生成了對應的Mapper Source/Class。這種方式是最利于跟蹤調(diào)試的。至于發(fā)布部署,和JDO的情況差不多,不如Hibernate的動態(tài)代碼增強。
這里我很羨慕Python, Ruby等動態(tài)解釋語言的
這一層我主要關注的是性能,緩存策略等等,而不是簡便。我覺得,一個應用系統(tǒng)的瓶頸主要存在于O/R, DB層。不應該單純?yōu)榱俗非驩O結(jié)構(gòu)的優(yōu)雅,或者編程的方便,而犧牲了一些可能優(yōu)化的地方。
關于Lightor的緩存策略, 我的Blog上有幾篇文章。
http://blog.csdn.net/buaawhl
數(shù)據(jù)庫對象的緩存策略
http://blog.csdn.net/buaawhl/archive/2004/12/21/224184.aspx
分頁 & QueryKey & 定長預取
http://blog.csdn.net/buaawhl/archive/2005/01/08/245005.aspx
8.總結(jié)
我理想中的Web開發(fā)架構(gòu)是這樣的:
開發(fā)速度快,運行速度快,結(jié)構(gòu)清晰優(yōu)雅。
具體到每一層。
Web框架層主要追求 開發(fā)速度快。
O/R層主要追求 運行速度快。
頁面資源層和頁面模板層主要追求 結(jié)構(gòu)清晰優(yōu)雅。