標(biāo)簽庫(kù),幾乎是每個(gè)MVC框架的重要組成部分。從Struts1開(kāi)始,到Webwork2,SpringMVC,都有自己的定義的一套標(biāo)簽庫(kù)。所以,一度標(biāo)簽庫(kù)的使用,會(huì)成為一個(gè)框架初學(xué)者判定一個(gè)Web框架好壞的重要標(biāo)準(zhǔn)之一。我也曾經(jīng)見(jiàn)到過(guò)許多Web開(kāi)發(fā)人員,長(zhǎng)期地存在著一定的誤解:只要學(xué)好標(biāo)簽庫(kù),能夠?qū)?biāo)簽庫(kù)熟記于心,那么這個(gè)框架我就算掌握了七八成。
標(biāo)簽庫(kù),只是框架的一部分,為了解決頁(yè)面顯示數(shù)據(jù)、封裝簡(jiǎn)單頁(yè)面邏輯而產(chǎn)生的類(lèi)HTML標(biāo)記的組件。
所以千萬(wàn)不要把標(biāo)簽的作用神化,也不要認(rèn)為學(xué)習(xí)標(biāo)簽是學(xué)習(xí)Web框架的重中之重,因?yàn)樗麄冎皇强蚣苤械男⌒∫徊糠帧?
在JSP誕生之初,JSP提供了在HTML代碼中嵌入Java代碼的特性。這種特性使得我們可以比較容易的利用Java這種強(qiáng)類(lèi)型語(yǔ)言的優(yōu)勢(shì),完成許多復(fù)雜的業(yè)務(wù)邏輯。
不過(guò)隨著時(shí)間的推移,我們發(fā)現(xiàn)在HTML代碼中嵌入過(guò)多的Java代碼,非常不利于JSP的維護(hù)和擴(kuò)展。對(duì)于動(dòng)輒上千行的JSP代碼,很多時(shí)候,程序員基本喪失了對(duì)JSP的維護(hù)能力?;谏鲜龅倪@個(gè)問(wèn)題,我們嘗試使用一種新的技術(shù),來(lái)解決上面這些問(wèn)題。這也就是標(biāo)簽產(chǎn)生的初衷:
1. 盡量避免在JSP頁(yè)面中使用Java代碼,而改用類(lèi)似HTML的標(biāo)簽的形式來(lái)表達(dá)頁(yè)面邏輯,讓邏輯與顯示分離,提高JSP的可維護(hù)性
2. 由于HTML自身的標(biāo)簽的表達(dá)能力不足,通過(guò)使用JSP Tag,可以對(duì)HTML語(yǔ)義進(jìn)行擴(kuò)展,從而完成許多HTML自身標(biāo)簽無(wú)法完成的工作
有關(guān)標(biāo)簽,爭(zhēng)論一直存在著。早在2004年的時(shí)候,robbin同學(xué)以一文
《炮打Taglib,我的一張大字報(bào)!》引起了許多人對(duì)Taglib的討論。當(dāng)時(shí),robbin同學(xué)的觀點(diǎn)大概是這樣的:
robbin 寫(xiě)道
我認(rèn)為JSP里面使用Tag,就是一個(gè)錯(cuò)誤!我反對(duì)在JSP里面使用Tag,我推薦大家在JSP里面寫(xiě)Java代碼,沒(méi)錯(cuò),就是在JSP里面寫(xiě)Java代碼,我就是一直這么干!
從Sun在JSP里面引入Taglib,我就認(rèn)為他是一個(gè)謊言!我認(rèn)為大家都被Sun欺騙了,我做JSP編程,但凡我寫(xiě)過(guò)的JSP,我從來(lái)不用Tag,我覺(jué)得寫(xiě)java代碼讓我很舒服,我不需要再去學(xué)習(xí)那別扭而無(wú)意義的Tag語(yǔ)法,來(lái)增加我的工作量,來(lái)增加我的JSP頁(yè)面調(diào)試難度。
不知道時(shí)隔快5年,robbin的觀點(diǎn)是否有所變化。在這5年中,JSP的表示層方案實(shí)際上并沒(méi)有發(fā)生很大的變化。Taglib也并沒(méi)有消亡,AJAX技術(shù)更加蓬勃的發(fā)展,模板技術(shù)已經(jīng)被視為成熟并可供使用的重要表示層技術(shù)。
標(biāo)簽,到底是解藥還是毒藥?它到底能不能為View層開(kāi)發(fā)帶來(lái)便捷?這個(gè)問(wèn)題,我們需要更加辯證的來(lái)看待。我們首先將所有有關(guān)標(biāo)簽的觀點(diǎn)總結(jié)成正反兩派:
正方 1. 標(biāo)簽產(chǎn)生的初衷沒(méi)有錯(cuò),它的存在,能夠簡(jiǎn)化JSP開(kāi)發(fā)的難度,并對(duì)HTML的許多標(biāo)簽進(jìn)行功能擴(kuò)展 2. 標(biāo)簽從效果上的確在一定程度上解決了在JSP頁(yè)面中,避免使用Java代碼的情況 3. 由于Java的語(yǔ)法與表現(xiàn)能力上的優(yōu)勢(shì),使用Taglib能夠極大程度的封裝成塊的HTML代碼,從而形成一套完整的頁(yè)面組件,能夠極大的簡(jiǎn)化頁(yè)面開(kāi)發(fā) 反方 1. 標(biāo)簽只是為了嘗試避免在JSP頁(yè)面中使用Java代碼,實(shí)際上,這種情況很難避免。有時(shí)候,為了達(dá)到這個(gè)目的,反而帶來(lái)了更多的代碼和沉重的維護(hù)成本。一個(gè)很簡(jiǎn)單的理由,編寫(xiě)一個(gè)Tag,至少需要一個(gè)tld文件定義、一個(gè)Java的實(shí)現(xiàn)類(lèi)、JSP中去聲明tld并編寫(xiě)Tag。顯然,對(duì)于許多情況而言,這樣所帶來(lái)的代碼量,反而超過(guò)了在頁(yè)面上直接使用Java代碼 2. 不僅如此,標(biāo)簽的存在為廣大程序員帶來(lái)了無(wú)窮無(wú)盡的學(xué)習(xí)成本。因?yàn)楦鶕?jù)使用的框架的不同,會(huì)帶來(lái)不同的標(biāo)簽庫(kù)。再加上許多公司在標(biāo)簽庫(kù)為基礎(chǔ)的頁(yè)面組件方面的積累,公司內(nèi)部就有一套標(biāo)簽庫(kù),于是學(xué)習(xí)這些標(biāo)簽的語(yǔ)法已經(jīng)成為了程序員的沉重負(fù)擔(dān) 3. 對(duì)于以標(biāo)簽庫(kù)為基礎(chǔ)的頁(yè)面組件,在面對(duì)經(jīng)常變化的頁(yè)面邏輯時(shí),顯得無(wú)能為力。因?yàn)樗木S護(hù)成本極高,當(dāng)邏輯變化頻繁時(shí),這些頁(yè)面組件的表現(xiàn)能力,還不如直接通過(guò)JavaScript操作HTML DOM來(lái)的方便 為此,正反雙方也是各執(zhí)一詞,各有道理。在這點(diǎn)上,JSF是強(qiáng)烈站在正方觀點(diǎn)上的一個(gè)Web層框架,它試圖通過(guò)IDE的幫助,來(lái)解決反方所說(shuō)的維護(hù)成本和學(xué)習(xí)成本的問(wèn)題。而許多其他的Web框架,則相對(duì)保持中立,對(duì)于標(biāo)簽庫(kù),采取“不拋棄,不放棄”的原則。
如果我們比較理性的來(lái)看待這個(gè)問(wèn)題,也只能說(shuō),這是一個(gè)編程方面的哲學(xué)問(wèn)題。每個(gè)人應(yīng)該根據(jù)自己對(duì)待代碼的哲學(xué)理解進(jìn)行答案選擇。千萬(wàn)不要因此而引起框架之爭(zhēng)。 在深入探討每個(gè)框架的標(biāo)簽之前,我們先來(lái)為標(biāo)簽分一下類(lèi),我們分類(lèi)的標(biāo)準(zhǔn),主要是根據(jù)標(biāo)簽的作用。
邏輯控制類(lèi) 以JSTL為例,JSTL大概提供了以下一些標(biāo)簽,用于邏輯控制的標(biāo)簽:
- c:if ———— 分支判斷
- c:forEach ———— 循環(huán)
- c:choose / c:when / c:otherwise ———— 分支判斷
- c:catch ———— 異常處理
邏輯控制類(lèi)的標(biāo)簽實(shí)際上每個(gè)框架基本上都有,因?yàn)檫壿嬁刂剖墙M成程序的基本要素,
可以說(shuō),邏輯控制類(lèi)的標(biāo)簽在JSP中,也是必不可少的。只是不同的Web框架對(duì)邏輯控制類(lèi)的標(biāo)簽的定義稍有不同,其本質(zhì)還是一樣的。
數(shù)據(jù)輸出類(lèi) 同樣以JSTL為例,JSTL大概提供了以下幾個(gè)用于數(shù)據(jù)輸出的標(biāo)簽:
- c:out ———— 輸出表達(dá)式的值
- c:url ———— 輸出格式化url
- c:set ———— 設(shè)置表達(dá)式的值
- c:param ———— 設(shè)置參數(shù)
- fmt:message ———— 輸出資源文件中的值
- fmt:formatDate ———— 格式化輸出日期
- fmt:formatNumber ———— 格式化輸出日期
數(shù)據(jù)輸出類(lèi)的標(biāo)簽每個(gè)框架也都有。比如Struts1中,著名的<bean:property>標(biāo)簽、webwork中,著名的<ww:property>標(biāo)簽等等。
這些標(biāo)簽對(duì)于JSP頁(yè)面來(lái)說(shuō),也是必不可少的。因?yàn)镴SP的基本作用就是展現(xiàn)數(shù)據(jù),這些標(biāo)簽正是為了展現(xiàn)數(shù)據(jù)而提供的。
頁(yè)面組件類(lèi) 頁(yè)面組件類(lèi)的Taglib就更多了,以Struts2為例,Struts2提供了無(wú)數(shù)UI Tag,具體可以參考:
http://struts.apache.org/2.0.14/docs/tag-reference.html 這些UI Tag實(shí)際上是對(duì)HTML標(biāo)簽的擴(kuò)展和擴(kuò)充,添加框架本身特有的特性。所以,這類(lèi)標(biāo)簽,如果使用得當(dāng),將為開(kāi)發(fā)帶來(lái)極大的便捷。如果這類(lèi)標(biāo)簽使用不當(dāng),則會(huì)帶來(lái)很大的學(xué)習(xí)成本和維護(hù)成本。
事實(shí)上,這些標(biāo)簽對(duì)于JSP來(lái)說(shuō),并不是必須的,只是對(duì)UI的開(kāi)發(fā)起到輔助的作用。 小結(jié) 除了上述這3類(lèi)Taglib以外,我們還會(huì)有很多綜合類(lèi)的Taglib,這些Taglib往往都是為了解決特定的頁(yè)面邏輯或者頁(yè)面展示而做出的自定義的標(biāo)簽。
在我們仔細(xì)分析了標(biāo)簽的分類(lèi)之后,我們能夠發(fā)現(xiàn),
前兩類(lèi)的標(biāo)簽實(shí)際上對(duì)于一個(gè)框架和JSP開(kāi)發(fā)而言,是具備價(jià)值的,也是必不可缺的元素,而對(duì)于后一類(lèi)的標(biāo)簽,我們應(yīng)該謹(jǐn)慎對(duì)待。事實(shí)上,這些標(biāo)簽往往可以通過(guò)其他的方式代替。我想許多針對(duì)標(biāo)簽的反對(duì)的聲音,絕大多數(shù)也來(lái)自于最自定義的頁(yè)面組件標(biāo)簽的抨擊。如果你在UI的標(biāo)簽上,表現(xiàn)出了學(xué)習(xí)上的不適應(yīng)性,那么你完全可以思考著采取其他的方案來(lái)代替,而不是在一條死胡同里面。
首先,我并不想在這里舉出非常具體的例子來(lái)說(shuō)明每一個(gè)標(biāo)簽中每個(gè)屬性的用法,因?yàn)檫@些東西其實(shí)大家都可以通過(guò)API手冊(cè)獲得,我希望列出一些在設(shè)計(jì)思想上有所差異的標(biāo)簽種類(lèi),并細(xì)細(xì)品味一下這些Web層標(biāo)簽的設(shè)計(jì)初衷和設(shè)計(jì)思想。
Struts / Webwork Struts和Webwork的標(biāo)簽是最傳統(tǒng)的標(biāo)簽。按照我上面所說(shuō)的分類(lèi),Struts和Webwork基本上在每個(gè)分類(lèi)上都有相應(yīng)的標(biāo)簽實(shí)現(xiàn)。不僅如此,Struts2還在頁(yè)面組件化上下了點(diǎn)功夫,將許多頁(yè)面組件定義成了標(biāo)簽的實(shí)現(xiàn)。我個(gè)人對(duì)Struts2的這種做法持保留態(tài)度,因?yàn)門(mén)aglib畢竟在擴(kuò)展性和實(shí)用性上不如直接使用JavaScript等成熟的框架來(lái)的方便。
不過(guò)Struts和Webwork的標(biāo)簽相對(duì)來(lái)說(shuō)還是比較好用的,因?yàn)樗脑S多內(nèi)部實(shí)現(xiàn)與HTML自身的標(biāo)簽屬性非常一致,降低了程序員學(xué)習(xí)的成本。
JSTL JSTL也是時(shí)下非常流行的一個(gè)標(biāo)簽庫(kù)。由于JSTL是Sun力推的標(biāo)準(zhǔn)化標(biāo)簽庫(kù),所以其剛剛誕生,就奠定了其試圖統(tǒng)一標(biāo)簽世界的想法。不過(guò)實(shí)際上,從標(biāo)簽的分類(lèi)中,我們也可以看到,JSTL主要實(shí)現(xiàn)的內(nèi)容,是第一類(lèi)和第二類(lèi)的標(biāo)簽,而沒(méi)有第三類(lèi)標(biāo)簽。按照它的想法,第三類(lèi)的標(biāo)簽應(yīng)該由用戶(hù)自己去實(shí)現(xiàn),或者采用其他的替代方案。
JSTL的好處是毋庸置疑的。它會(huì)依次從page、request、session、application作用域中查找表達(dá)式的值,使得它能夠兼容一切Web框架。因?yàn)樗械腤eb框架基本上還是圍繞著這些Servlet對(duì)象來(lái)打交道。另外一方面,JSTL的標(biāo)簽數(shù)量比較少,學(xué)習(xí)成本很低,所以很多程序員都喜歡學(xué)習(xí)它。
Tapestry Tapestry的思路與其他Web框架實(shí)現(xiàn)標(biāo)簽的思路全都不同。Tapestry提倡所有的頁(yè)面都HTML化,不應(yīng)引入額外的HTML標(biāo)簽,如果你需要實(shí)現(xiàn)頁(yè)面邏輯,應(yīng)該以組件的形式完成,而這些組件的頁(yè)面調(diào)用方式,是在HTML標(biāo)簽中加入一些自定義的屬性。
- <table>
- <tr>
- <td>User Name</td>
- <td>Action</td>
- </tr>
- <tr jwcid="@Foreach" source="ognl:users" element="tr" value="ognl:user">
- <td><span jwcid="@Insert" value="ognl:user.name">Quake Wang</span></td>
- <td><a href="#" jwcid="@ActionLink" listener="ognl:listeners.deleteUser">Delete</a> <a href="#" jwcid="@ActionLink" listener="ognl:listeners.updateUser">Update</a></td>
- </tr>
- </table>
<table><tr><td>User Name</td><td>Action</td></tr><tr jwcid="@Foreach" source="ognl:users" element="tr" value="ognl:user"><td><span jwcid="@Insert" value="ognl:user.name">Quake Wang</span></td><td><a href="#" jwcid="@ActionLink" listener="ognl:listeners.deleteUser">Delete</a> <a href="#" jwcid="@ActionLink" listener="ognl:listeners.updateUser">Update</a></td></tr></table>
這里我引用了Quake Wang同學(xué)曾經(jīng)使用過(guò)的例子。雖然這個(gè)代碼還是Tapestry3時(shí)代的代碼,但是我們可以從其中看到許多Tapestry在標(biāo)簽設(shè)計(jì)上的獨(dú)到之處。
它沒(méi)有額外的標(biāo)簽定義,但是有額外的標(biāo)簽內(nèi)屬性定義,并通過(guò)框架,將這些定義與后臺(tái)組件聯(lián)系在一起。 這或許也就是許多Tapestry程序員對(duì)這個(gè)Web框架鐘愛(ài)的原因,由于沒(méi)有額外的JSP標(biāo)簽定義,那么頁(yè)面展示使用HTML也就夠了。所以,所有的程序員都號(hào)稱(chēng)Tapestry是對(duì)美工最友好的Web框架。
我個(gè)人對(duì)待上述觀點(diǎn)是無(wú)法認(rèn)同的,理由非常簡(jiǎn)單,額外定義標(biāo)簽,與標(biāo)簽中額外定義屬性,從本質(zhì)上來(lái)說(shuō),區(qū)別不是很大。從學(xué)習(xí)成本而言,也陡然增加了程序員學(xué)習(xí)組件的成本。對(duì)于美工呢?他們能忽略這些額外的標(biāo)簽嘛?理論上,可以,不過(guò)如果大家試試看一個(gè)比較復(fù)雜的帶有多處分支判斷的頁(yè)面,美工MM同樣會(huì)非常痛苦。
總體來(lái)說(shuō),Tapestry的標(biāo)簽具有它的特色,從設(shè)計(jì)上也有巧思之處。如果我們將這種思想移植到普通的JSP里面來(lái),其實(shí)我們完全也可以使用類(lèi)似的方式,在HTML標(biāo)簽中定義額外的屬性,并使用JavaScript來(lái)控制這些屬性的行為,同樣可以形成頁(yè)面組件。