<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-128  評(píng)論-55  文章-5  trackbacks-0
    原文:http://home.phpchina.com/space.php?uid=108994&do=blog&id=69516

    接口& 組件、容器

    1. J2ee與接口

    每一個(gè)版本的J2ee都對(duì)應(yīng)著一個(gè)確定版本的JDK,J2ee1.4對(duì)應(yīng)Jdk1.4,現(xiàn)在比較新的是JDK5.0,自然也會(huì)有J2EE 5.0。其實(shí)筆者一直在用的是J2EE1.4,不過(guò)沒(méi)什么關(guān)系,大家可以下任何一個(gè)版本的J2ee api來(lái)稍微瀏覽一下。筆者想先聲明一個(gè)概念,J2ee也是源自Java,所以底層的操作依然調(diào)用到很多J2se的庫(kù),所以才建議大家先牢牢掌握J(rèn)2se 的主流技術(shù)。

    J2ee api有一個(gè)特點(diǎn),大家比較熟悉的幾個(gè)包java.jms、javax.servlet.http、javax.ejb等都以interface居多,實(shí)現(xiàn)類(lèi)較少。其實(shí)大家真正在用的時(shí)候百分之六十以上都在反復(fù)的查著javax.servlet.http這個(gè)包下面幾個(gè)實(shí)現(xiàn)類(lèi)的api函數(shù),其他的包很少問(wèn)津。筆者建議在學(xué)習(xí)一種技術(shù)之前,對(duì)整體的框架有一個(gè)了解是很有必要的,J2ee旨在通過(guò)interface的聲明來(lái)規(guī)范實(shí)現(xiàn)的行為,任何第三方的廠商想要提供自己品牌的實(shí)現(xiàn)前提也是遵循這些接口定義的規(guī)則。如果在從前J2se學(xué)習(xí)的道路上對(duì)接口的理解很好的話,這里的體會(huì)將是非常深刻的,舉個(gè)簡(jiǎn)單的例子:

    public interface Mp3{
    public void play();
    public void record();
    public void stop();
    }

    如果我定義這個(gè)簡(jiǎn)單的接口,發(fā)布出去,規(guī)定任何第三方的公司想推出自己的名字為Mp3的產(chǎn)品都必須實(shí)現(xiàn)這個(gè)接口,也就是至少提供接口中方法的具體實(shí)現(xiàn)。這個(gè)意義已經(jīng)遠(yuǎn)遠(yuǎn)不止是面向?qū)ο蟮亩鄳B(tài)了,只有廠商遵循J2ee的接口定義,世界上的J2ee程序員才能針對(duì)統(tǒng)一的接口進(jìn)行程序設(shè)計(jì),最終不用改變代碼只是因?yàn)槭褂昧瞬煌瑥S商的實(shí)現(xiàn)類(lèi)而有不同的特性罷了,本質(zhì)上說(shuō),無(wú)論哪一種廠商實(shí)現(xiàn)都完成了職責(zé)范圍內(nèi)的工作。這個(gè)就是筆者想一直強(qiáng)調(diào)的,針對(duì)接口編程的思想。

    接口到底有什么好處呢?我們這樣設(shè)想,現(xiàn)在有AppleMp3、SonyMp3、SamsungMp3都實(shí)現(xiàn)了這個(gè)Mp3的接口,于是都有了play、 record、stop這三個(gè)功能。我們將Mp3產(chǎn)品座位一個(gè)組件的時(shí)候就不需要知道它的具體實(shí)現(xiàn),只要看到接口定義知道這個(gè)對(duì)象有3個(gè)功能就可以使用了。那么類(lèi)似下面這樣的業(yè)務(wù)就完全可以在任何時(shí)間從3個(gè)品牌擴(kuò)展到任意個(gè)品牌,開(kāi)個(gè)玩笑的說(shuō),項(xiàng)目經(jīng)理高高在上的寫(xiě)完10個(gè)接口里的方法聲明,然后就丟給手下的程序員去寫(xiě)里面的細(xì)節(jié),由于接口已經(jīng)統(tǒng)一(即每個(gè)方法傳入和傳出的格式已經(jīng)統(tǒng)一),經(jīng)理只需關(guān)注全局的業(yè)務(wù)就可以天天端杯咖啡走來(lái)走去了,^_^:
    public Mp3 create();
    public void copy(Mp3 mp3);
    public Mp3 getMp3();

    最后用一個(gè)簡(jiǎn)單的例子說(shuō)明接口:一個(gè)5號(hào)電池的手電筒,可以裝入任何牌子的5號(hào)電池,只要它符合5號(hào)電池的規(guī)范,裝入之后任何看不到是什么牌子,只能感受到手電筒在完成它的功能。那么生產(chǎn)手電筒的廠商和生產(chǎn)5號(hào)電池的廠商就可以完全解除依賴關(guān)系,可以各自自由開(kāi)發(fā)自己的產(chǎn)品,因?yàn)樗鼈兌甲袷?號(hào)電池應(yīng)有的形狀、正負(fù)極位置等約定。這下大家能對(duì)接口多一點(diǎn)體會(huì)了么?

    2. 組件和容器
    針對(duì)接口是筆者特意強(qiáng)調(diào)的J2ee學(xué)習(xí)之路必備的思想,另外一個(gè)就是比較常規(guī)的組件和容器的概念了。很多教材和專(zhuān)業(yè)網(wǎng)站都說(shuō)J2EE的核心是一組規(guī)范與指南,強(qiáng)調(diào)J2ee的核心概念就是組件+容器,這確實(shí)是無(wú)可厚非的。隨著越來(lái)越多的J2ee框架出現(xiàn),相應(yīng)的每種框架都一般有與之對(duì)應(yīng)的容器。

    容器,是用來(lái)管理組件行為的一個(gè)集合工具,組件的行為包括與外部環(huán)境的交互、組件的生命周期、組件之間的合作依賴關(guān)系等等。J2ee包含的容器種類(lèi)大約有 Web容器、Application Client容器、EJB容器、Applet客戶端容器等。但在筆者看來(lái),現(xiàn)在容器的概念變得有點(diǎn)模糊了,大家耳熟能詳是那些功能強(qiáng)大的開(kāi)源框架,比如 Hibernate、Struts2、Spring、JSF等,其中Hibernate就基于JDBC的基礎(chǔ)封裝了對(duì)事務(wù)和會(huì)話的管理,大大方便了對(duì)數(shù)據(jù)庫(kù)操作的繁瑣代碼,從這個(gè)意義上來(lái)說(shuō)它已經(jīng)接近容器的概念了,EJB的實(shí)體Bean也逐漸被以Hibernate為代表的持久化框架所取代。

    組件,本意是指可以重用的代碼單元,一般代表著一個(gè)或者一組可以獨(dú)立出來(lái)的功能模塊,在J2ee中組件的種類(lèi)有很多種,比較常見(jiàn)的是EJB組件、DAO組件、客戶端組件或者應(yīng)用程序組件等,它們有個(gè)共同特點(diǎn)是分別會(huì)打包成.war,.jar,.jar,.ear,每個(gè)組件由特定格式的xml描述符文件進(jìn)行描述,而且服務(wù)器端的組件都需要被部署到應(yīng)用服務(wù)器上面才能夠被使用。

    稍微理解完組件和容器,還有一個(gè)重要的概念就是分層模型,最著名的當(dāng)然是MVC三層模型。在一個(gè)大的工程或項(xiàng)目中,為了讓前臺(tái)和后臺(tái)各個(gè)模塊的編程人員能夠同時(shí)進(jìn)行工作提高開(kāi)發(fā)效率,最重要的就是實(shí)現(xiàn)層與層之間的耦合關(guān)系,許多分層模型的宗旨和開(kāi)源框架所追求的也就是這樣的效果。在筆者看來(lái),一個(gè)完整的 Web項(xiàng)目大概有以下幾個(gè)層次:

    a) 表示層(Jsp、Html、Javascript、Ajax、Flash等等技術(shù)對(duì)其支持)
    b) 控制層(Struts、JSF、WebWork等等框架在基于Servlet的基礎(chǔ)上支持,負(fù)責(zé)把具體的請(qǐng)求數(shù)據(jù)(有時(shí)卸載重新裝載)導(dǎo)向適合處理它的模型層對(duì)象)
    c) 模型層(筆者認(rèn)為目前最好的框架是Spring,實(shí)質(zhì)就是處理表示層經(jīng)由控制層轉(zhuǎn)發(fā)過(guò)來(lái)的數(shù)據(jù),包含著大量的業(yè)務(wù)邏輯)
    d) 數(shù)據(jù)層(Hibernate、JDBC、EJB等,由模型層處理完了持久化到數(shù)據(jù)庫(kù)中)


    Servlet/Jsp
    終于正式進(jìn)入J2ee的細(xì)節(jié)部分了,首當(dāng)其沖的當(dāng)然是Servlet和Jsp了,上篇曾經(jīng)提到過(guò)J2ee只是一 個(gè)規(guī)范和指南,定義了一組必須要遵循的接口,核心概念是組件和容器。曾經(jīng)有的人問(wèn)筆者Servlet的Class文件是哪里來(lái)的?他認(rèn)為是J2ee官方提 供的,我舉了一個(gè)簡(jiǎn)單的反例:稍微檢查了一下Tomcat5.0里面的Servlet.jar文件和JBoss里面的Servlet.jar文件大小,很 明顯是不一樣的,至少已經(jīng)說(shuō)明了它們不是源自同根的吧。其實(shí)Servlet是由容器根據(jù)J2ee的接口定義自己來(lái)實(shí)現(xiàn)的,實(shí)現(xiàn)的方式當(dāng)然可以不同,只要都 遵守J2ee規(guī)范和指南。

    上述只是一個(gè)常見(jiàn)的誤區(qū)罷了,告訴我們要編譯運(yùn)行Servlet,是要依賴于實(shí)現(xiàn)它的容器的,不然連jar文件都沒(méi)有,編譯都無(wú)法進(jìn) 行。那么Jsp呢? Java Server Page的簡(jiǎn)稱(chēng),是為了開(kāi)發(fā)動(dòng)態(tài)網(wǎng)頁(yè)而誕生的技術(shù),其本質(zhì)也是Jsp,在編寫(xiě)完畢之后會(huì)在容器啟動(dòng)時(shí)經(jīng)過(guò)編譯成對(duì)應(yīng)的Servlet。只是我們利用Jsp 的很多新特性,可以更加專(zhuān)注于前后臺(tái)的分離,早期Jsp做前臺(tái)是滿流行的,畢竟里面支持Html代碼,這讓前臺(tái)美工人員可以更有效率的去完成自己的工作。 然后Jsp將請(qǐng)求轉(zhuǎn)發(fā)到后臺(tái)的Servlet,由Servlet處理業(yè)務(wù)邏輯,再轉(zhuǎn)發(fā)回另外一個(gè)Jsp在前臺(tái)顯示出來(lái)。這似乎已經(jīng)成為一種常用的模式,最 初筆者學(xué)習(xí)J2ee的時(shí)候,大量時(shí)間也在編寫(xiě)這樣的代碼。

    盡管現(xiàn)在做前臺(tái)的技術(shù)越來(lái)越多,例如Flash、Ajax等,已經(jīng)有很多人不再認(rèn)為Jsp重要了。筆者覺(jué)得Jsp帶來(lái)的不僅僅是前后 端分離的設(shè)計(jì)理念,它的另外一項(xiàng)技術(shù)成就了我們今天用的很多框架,那就是Tag標(biāo)簽技術(shù)。所以與其說(shuō)是在學(xué)習(xí)Jsp,不如更清醒的告訴自己在不斷的理解 Tag標(biāo)簽的意義和本質(zhì)。

    1. Servlet以及Jsp的生命周期
    Servlet是Jsp的實(shí)質(zhì),盡管容器對(duì)它們的處理有所區(qū)別。Servlet有init()方法初始化,service()方法進(jìn)行Web服 務(wù), destroy()方法進(jìn)行銷(xiāo)毀,從生到滅都由容器來(lái)掌握,所以這些方法除非你想自己來(lái)實(shí)現(xiàn)Servlet,否則是很少會(huì)接觸到的。正是由于很少接觸,才 容易被廣大初學(xué)者所忽略,希望大家至少記住Servlet生命周期方法都是回調(diào)方法?;卣{(diào)這個(gè)概念簡(jiǎn)單來(lái)說(shuō)就是把自己注入另外一個(gè)類(lèi)中,由它來(lái)調(diào)用你的方 法,所謂的另外一個(gè)類(lèi)就是Web容器,它只認(rèn)識(shí)接口和接口的方法,注入進(jìn)來(lái)的是怎樣的對(duì)象不管,它只會(huì)根據(jù)所需調(diào)用這個(gè)對(duì)象在接口定義存在的那些方法。由 容器來(lái)調(diào)用的Servlet對(duì)象的初始化、服務(wù)和銷(xiāo)毀方法,所以叫做回調(diào)。這個(gè)概念對(duì)學(xué)習(xí)其他J2ee技術(shù)相當(dāng)關(guān)鍵!

    那么Jsp呢?本事上是Servlet,還是有些區(qū)別的,它的生命周期是這樣的:
    a) 一個(gè)客戶端的Request到達(dá)服務(wù)器 ->
    b) 判斷是否第一次調(diào)用 -> 是的話編譯Jsp成Servlet
    c) 否的話再判斷此Jsp是否有改變 -> 是的話也重新編譯Jsp成Servlet
    d) 已經(jīng)編譯最近版本的Servlet裝載所需的其他Class
    e) 發(fā)布Servlet,即調(diào)用它的Service()方法

    所以Jsp號(hào)稱(chēng)的是第一次Load緩慢,以后都會(huì)很快的運(yùn)行。從它的生命的周期確實(shí)不難看出來(lái)這個(gè)特點(diǎn),客戶端的操作很少會(huì)改變Jsp的 源碼,所以它不需要編譯第二次就一直可以為客戶端提供服務(wù)。這里稍微解釋一下Http的無(wú)狀態(tài)性,因?yàn)榘l(fā)現(xiàn)很多人誤解,Http的無(wú)狀態(tài)性是指每次一張頁(yè) 面顯示出來(lái)了,與服務(wù)器的連接其實(shí)就已經(jīng)斷開(kāi)了,當(dāng)再次有提交動(dòng)作的時(shí)候,才會(huì)再次與服務(wù)器進(jìn)行連接請(qǐng)求提供服務(wù)。當(dāng)然還有現(xiàn)在比較流行的是Ajax與服 務(wù)器異步通過(guò) xml交互的技術(shù),在做前臺(tái)的領(lǐng)域潛力巨大,筆者不是Ajax的高手,這里無(wú)法為大家解釋。

    2. Tag標(biāo)簽的本質(zhì)
    筆者之前說(shuō)了,Jsp本身初衷是使得Web應(yīng)用前后臺(tái)的開(kāi)發(fā)可以脫離耦合分開(kāi)有效的進(jìn)行,可惜這個(gè)理念的貢獻(xiàn)反倒不如它帶來(lái)的Tag技術(shù)對(duì) J2ee的貢獻(xiàn)要大。也許已經(jīng)有很多人開(kāi)始使用Tag技術(shù)了卻并不了解它。所以才建議大家在學(xué)習(xí)J2ee開(kāi)始的時(shí)候一定要認(rèn)真學(xué)習(xí)Jsp,其實(shí)最重要的就 是明白標(biāo)簽的本質(zhì)。

    Html標(biāo)簽我們都很熟悉了,有 <html> 、 <head> 、 <body> 、 <title> ,Jsp帶來(lái)的Tag標(biāo)簽遵循同樣的格式,或者說(shuō)更嚴(yán)格的Xml格式規(guī)范,例如 <jsp:include> 、 <jsp:useBean> 、 <c:if> 、 <c:forEach> 等等。它們沒(méi)有什么神秘的地方,就其源頭也還是Java Class而已,Tag標(biāo)簽的實(shí)質(zhì)也就是一段Java代碼,或者說(shuō)一個(gè)Class文件。當(dāng)配置文件設(shè)置好去哪里尋找這些Class的路徑后,容器負(fù)責(zé)將頁(yè)面中存在的標(biāo)簽對(duì)應(yīng)到相應(yīng)的Class上,執(zhí)行那段特定的Java代碼,如此而已。
    說(shuō)得明白一點(diǎn)的話還是舉幾個(gè)簡(jiǎn)單的例子說(shuō)明一下吧:

    <jsp:include> 去哪里找執(zhí)行什么class呢?首先這是個(gè)jsp類(lèi)庫(kù)的標(biāo)簽,當(dāng)然要去jsp類(lèi)庫(kù)尋找相應(yīng)的class了,同樣它也是由Web容器來(lái)提供,例如 Tomcat就應(yīng)該去安裝目錄的lib文件夾下面的jsp-api.jar里面找,有興趣的可以去找一找啊!

    <c:forEach> 又去哪里找呢?這個(gè)是由Jsp2.0版本推薦的和核心標(biāo)記庫(kù)的內(nèi)容,例如 <c:if> 就對(duì)應(yīng)在頁(yè)面中做if判斷的功能的一斷Java代碼。它的class文件在jstl.jar這個(gè)類(lèi)庫(kù)里面,往往還需要和一個(gè)standard.jar類(lèi)庫(kù)一起導(dǎo)入,放在具體Web項(xiàng)目的WEB-INF的lib目錄下面就可以使用了。

    順便羅唆一句,Web Project的目錄結(jié)構(gòu)是相對(duì)固定的,因?yàn)槿萜鲿?huì)按照固定的路徑去尋找它需要的配置文件和資源,這個(gè)任何一本J2ee入門(mén)書(shū)上都有,這里就不介紹了。了 解Tag的本質(zhì)還要了解它的工作原理,所以大家去J2ee的API里找到并研究這個(gè)包:javax.servlet.jsp.tagext。它有一些接 口,和一些實(shí)現(xiàn)類(lèi),專(zhuān)門(mén)用語(yǔ)開(kāi)發(fā)Tag,只有自己親自寫(xiě)出幾個(gè)不同功能的標(biāo)簽,才算是真正理解了標(biāo)簽的原理。別忘記了自己開(kāi)發(fā)的標(biāo)簽要自己去完成配置文 件,容器只是集成了去哪里尋找jsp標(biāo)簽對(duì)應(yīng)class的路徑,自己寫(xiě)的標(biāo)簽庫(kù)當(dāng)然要告訴容器去哪里找啦。

    說(shuō)了這么多,我們?yōu)槭裁匆脴?biāo)簽?zāi)??完全在Jsp里面來(lái)個(gè) <% %> 就可以在里面任意寫(xiě)Java代碼了,但是長(zhǎng)期實(shí)踐發(fā)現(xiàn)頁(yè)面代碼統(tǒng)一都是與html同風(fēng)格的標(biāo)記語(yǔ)言更加有助于美工人員進(jìn)行開(kāi)發(fā)前臺(tái),它不需要懂Java, 只要Java程序員給個(gè)列表告訴美工什么標(biāo)簽可以完成什么邏輯功能,他就可以專(zhuān)注于美工,也算是進(jìn)一步隔離了前后臺(tái)的工作吧!

    3. 成就Web框架
    框架是什么?曾經(jīng)看過(guò)這樣的定義:與模式類(lèi)似,框架也是解決特定問(wèn)題的可重用方法,框架是一個(gè)描述性的構(gòu)建塊和服務(wù)集合,開(kāi)發(fā)人員可以用來(lái)達(dá)成某個(gè)目標(biāo)。一般來(lái)說(shuō),框架提供了解決某類(lèi)問(wèn)題的基礎(chǔ)設(shè)施,是用來(lái)創(chuàng)建解決方案的工具,而不是問(wèn)題的解決方案。

    正是由于Tag的出現(xiàn),成就了以后出現(xiàn)的那么多Web框架,它們都開(kāi)發(fā)了自己成熟實(shí)用的一套標(biāo)簽,然后由特定的Xml文件來(lái)配置加載信息,力圖使得Web 應(yīng)用的開(kāi)發(fā)變得更加高效。下面這些標(biāo)簽相應(yīng)對(duì)很多人來(lái)說(shuō)相當(dāng)熟悉了:
    <html:password>
    <logic:equal>
    <bean:write>
    <f:view>
    <h:form>
    <h:message>

    它們分別來(lái)自Struts和JSF框架,最強(qiáng)大的功能在于控制轉(zhuǎn)發(fā),就是MVC三層模型中間完成控制器的工作。Struts-1實(shí)際上并未做到真正的三層隔離,這一點(diǎn)在Struts-2上得到了很大的改進(jìn)。而Jsf向來(lái)以比較完善合理的標(biāo)簽庫(kù)受到人們推崇。

    今天就大概講這么多吧,再次需要強(qiáng)調(diào)的是Servlet/Jsp是學(xué)習(xí)J2ee必經(jīng)之路,也是最基礎(chǔ)的知識(shí),希望大家給與足夠的重視!



    Struts
    J2ee的開(kāi)源框架很多,筆者只能介紹自己熟悉的幾個(gè),其他的目前在中國(guó)IT行業(yè)應(yīng)用得不是很多。希望大家對(duì)新出的框架不要盲目的推崇,首先一定要熟悉它比舊的到底好在哪里,新的理念和特性是什么?然后再?zèng)Q定是否要使用它。

    這期的主題是Struts,直譯過(guò)來(lái)是支架。Struts的第一個(gè)版本是在2001年5月發(fā)布的,它提供了一個(gè)Web應(yīng)用的解決方案,如何讓Jsp和 servlet共存去提供清晰的分離視圖和業(yè)務(wù)應(yīng)用邏輯的架構(gòu)。在Struts之前,通常的做法是在Jsp中加入業(yè)務(wù)邏輯,或者在Servlet中生成視圖轉(zhuǎn)發(fā)到前臺(tái)去。Struts帶著MVC的新理念當(dāng)時(shí)退出幾乎成為業(yè)界公認(rèn)的Web應(yīng)用標(biāo)準(zhǔn),于是當(dāng)代IT市場(chǎng)上也出現(xiàn)了眾多熟悉Struts的程序員。即使有新的框架再出來(lái)不用,而繼續(xù)用Struts的理由也加上了一條低風(fēng)險(xiǎn),因?yàn)橹型救绻_(kāi)發(fā)人員變動(dòng),很容易的招進(jìn)新的會(huì)Struts的IT民工啊, ^_^!

    筆者之前說(shuō)的都是Struts-1,因?yàn)樾鲁隽薙truts-2,使得每次談到Struts都必須注明它是Struts-1還是2。筆者先談比較熟悉的 Struts-1,下次再介紹一下與Struts-2的區(qū)別:

    1. Struts框架整體結(jié)構(gòu)
    Struts-1的核心功能是前端控制器,程序員需要關(guān)注的是后端控制器。前端控制器是是一個(gè)Servlet,在Web.xml中間配置所有 Request都必須經(jīng)過(guò)前端控制器,它的名字是ActionServlet,由框架來(lái)實(shí)現(xiàn)和管理。所有的視圖和業(yè)務(wù)邏輯隔離都是應(yīng)為這個(gè) ActionServlet, 它就像一個(gè)交通警察,所有過(guò)往的車(chē)輛必須經(jīng)過(guò)它的法眼,然后被送往特定的通道。所有,對(duì)它的理解就是分發(fā)器,我們也可以叫做Dispatcher,其實(shí)了解Servlet編程的人自己也可以寫(xiě)一個(gè)分發(fā)器,加上攔截request的Filter,其實(shí)自己實(shí)現(xiàn)一個(gè)struts框架并不是很困難。主要目的就是讓編寫(xiě)視圖的和后臺(tái)邏輯的可以脫離緊耦合,各自同步的完成自己的工作。

    那么有了ActionServlet在中間負(fù)責(zé)轉(zhuǎn)發(fā),前端的視圖比如說(shuō)是Jsp,只需要把所有的數(shù)據(jù)Submit,這些數(shù)據(jù)就會(huì)到達(dá)適合處理它的后端控制器Action,然后在里面進(jìn)行處理,處理完畢之后轉(zhuǎn)發(fā)到前臺(tái)的同一個(gè)或者不同的視圖Jsp中間,返回前臺(tái)利用的也是Servlet里面的forward 和redirect兩種方式。所以到目前為止,一切都只是借用了Servlet的API搭建起了一個(gè)方便的框架而已。這也是Struts最顯著的特性?? 控制器。

    那么另外一個(gè)特性,可以說(shuō)也是Struts-1帶來(lái)的一個(gè)比較成功的理念,就是以xml配置代替硬編碼配置信息。以往決定Jsp往哪個(gè)servlet提交,是要寫(xiě)進(jìn)Jsp代碼中的,也就是說(shuō)一旦這個(gè)提交路徑要改,我們必須改寫(xiě)代碼再重新編譯。而Struts提出來(lái)的思路是,編碼的只是一個(gè)邏輯名字,它對(duì)應(yīng)哪個(gè)class文件寫(xiě)進(jìn)了xml配置文件中,這個(gè)配置文件記錄著所有的映射關(guān)系,一旦需要改變路徑,改變xml文件比改變代碼要容易得多。這個(gè)理念可以說(shuō)相當(dāng)成功,以致于后來(lái)的框架都延續(xù)著這個(gè)思路,xml所起的作用也越來(lái)越大。

    大致上來(lái)說(shuō)Struts當(dāng)初給我們帶來(lái)的新鮮感就這么多了,其他的所有特性都是基于方便的控制轉(zhuǎn)發(fā)和可擴(kuò)展的xml配置的基礎(chǔ)之上來(lái)完成它們的功能的。
    下面將分別介紹Action和FormBean, 這兩個(gè)是Struts中最核心的兩個(gè)組件。

    2. 后端控制器Action
    Action就是我們說(shuō)的后端控制器,它必須繼承自一個(gè)Action父類(lèi),Struts設(shè)計(jì)了很多種Action,例如DispatchAction、 DynaValidationAction。它們都有一個(gè)處理業(yè)務(wù)邏輯的方法execute(),傳入的request, response, formBean和actionMapping四個(gè)對(duì)象,返回actionForward對(duì)象。到達(dá)Action之前先會(huì)經(jīng)過(guò)一個(gè) RequestProcessor來(lái)初始化配置文件的映射關(guān)系,這里需要大家注意幾點(diǎn):

    1) 為了確保線程安全,在一個(gè)應(yīng)用的生命周期中,Struts框架只會(huì)為每個(gè)Action類(lèi)創(chuàng)建一個(gè)Action實(shí)例,所有的客戶請(qǐng)求共享同一個(gè)Action 實(shí)例,并且所有線程可以同時(shí)執(zhí)行它的execute()方法。所以當(dāng)你繼承父類(lèi)Action,并添加了private成員變量的時(shí)候,請(qǐng)記住這個(gè)變量可以被多個(gè)線程訪問(wèn),它的同步必須由程序員負(fù)責(zé)。(所有我們不推薦這樣做)。在使用Action的時(shí)候,保證線程安全的重要原則是在Action類(lèi)中僅僅使用局部變量,謹(jǐn)慎的使用實(shí)例變量。局部變量是對(duì)每個(gè)線程來(lái)說(shuō)私有的,execute方法結(jié)束就被銷(xiāo)毀,而實(shí)例變量相當(dāng)于被所有線程共享。

    2) 當(dāng)ActionServlet實(shí)例接收到Http請(qǐng)求后,在doGet()或者doPost()方法中都會(huì)調(diào)用process()方法來(lái)處理請(qǐng)求。 RequestProcessor類(lèi)包含一個(gè)HashMap,作為存放所有Action實(shí)例的緩存,每個(gè)Action實(shí)例在緩存中存放的屬性key為 Action類(lèi)名。在RequestProcessor類(lèi)的processActionCreate()方法中,首先檢查在HashMap中是否存在 Action實(shí)例。創(chuàng)建Action實(shí)例的代碼位于同步代碼塊中,以保證只有一個(gè)線程創(chuàng)建Action實(shí)例。一旦線程創(chuàng)建了Action實(shí)例并把它存放到 HashMap中,以后所有的線程會(huì)直接使用這個(gè)緩存中的實(shí)例。

    3) <action> 元素的 <roles> 屬性指定訪問(wèn)這個(gè)Action用戶必須具備的安全角色,多個(gè)角色之間逗號(hào)隔開(kāi)。RequestProcessor類(lèi)在預(yù)處理請(qǐng)求時(shí)會(huì)調(diào)用自身的 processRoles()方法,檢查配置文件中是否為Action配置了安全角色,如果有,就調(diào)用HttpServletRequest的 isUserInRole()方法來(lái)判斷用戶是否具備了必要的安全性角色,如果不具備,就直接向客戶端返回錯(cuò)誤。(返回的視圖通過(guò) <input> 屬性來(lái)指定)

    3. 數(shù)據(jù)傳輸對(duì)象FormBean
    Struts并沒(méi)有把模型層的業(yè)務(wù)對(duì)象直接傳遞到視圖層,而是采用DTO(Data Transfer Object)來(lái)傳輸數(shù)據(jù),這樣可以減少傳輸數(shù)據(jù)的冗余,提高傳輸效率;還有助于實(shí)現(xiàn)各層之間的獨(dú)立,使每個(gè)層分工明確。Struts的DTO就是 ActionForm,即formBean。由于模型層應(yīng)該和Web應(yīng)用層保持獨(dú)立。由于ActionForm類(lèi)中使用了Servlet API, 因此不提倡把ActionForm傳遞給模型層, 而應(yīng)該在控制層把ActionForm Bean的數(shù)據(jù)重新組裝到自定義的DTO中, 再把它傳遞給模型層。它只有兩個(gè)scope,分別是session和request。(默認(rèn)是session)一個(gè)ActionForm標(biāo)準(zhǔn)的生命周期是:
    1) 控制器收到請(qǐng)求 ->
    2) 從request或session中取出ActionForm實(shí)例,如不存在就創(chuàng)建一個(gè) ->
    3) 調(diào)用ActionForm的reset()方法 ->
    4) 把實(shí)例放入session或者request中 ->
    5) 將用戶輸入表達(dá)數(shù)據(jù)組裝到ActionForm中 ->
    6) 如眼張方法配置了就調(diào)用validate()方法 ->
    7) 如驗(yàn)證錯(cuò)誤就轉(zhuǎn)發(fā)給 <input> 屬性指定的地方,否則調(diào)用execute()方法

    validate()方法調(diào)用必須滿足兩個(gè)條件:
    1) ActionForm 配置了Action映射而且name屬性匹配
    2) <aciton> 元素的validate屬性為true

    如果ActionForm在request范圍內(nèi),那么對(duì)于每個(gè)新的請(qǐng)求都會(huì)創(chuàng)建新的ActionForm實(shí)例,屬性被初始化為默認(rèn)值,那么reset ()方法就顯得沒(méi)有必要;但如果ActionForm在session范圍內(nèi),同一個(gè)ActionForm實(shí)例會(huì)被多個(gè)請(qǐng)求共享,reset()方法在這種情況下極為有用。

    4. 驗(yàn)證框架和國(guó)際化
    Struts有許多自己的特性,但是基本上大家還是不太常用,說(shuō)白了它們也是基于JDK中間的很多Java基礎(chǔ)包來(lái)完成工作。例如國(guó)際化、驗(yàn)證框架、插件自擴(kuò)展功能、與其他框架的集成、因?yàn)楦鞔罂蚣芑径加刑峁┻@樣的特性,Struts也并不是做得最好的一個(gè),這里也不想多說(shuō)。Struts的驗(yàn)證框架,是通過(guò)一個(gè)validator.xml的配置文件讀入驗(yàn)證規(guī)則,然后在validation-rules.xml里面找到驗(yàn)證實(shí)現(xiàn)通過(guò)自動(dòng)為Jsp插入 Javascript來(lái)實(shí)現(xiàn),可以說(shuō)做得相當(dāng)簡(jiǎn)陋。彈出來(lái)的JavaScript框不但難看還很多冗余信息,筆者寧愿用formBean驗(yàn)證或者 Action的saveErrors(),驗(yàn)證邏輯雖然要自己寫(xiě),但頁(yè)面隱藏/浮現(xiàn)的警告提示更加人性化和美觀一些。

    至于Struts的國(guó)際化,其實(shí)無(wú)論哪個(gè)框架的國(guó)際化,java.util.Locale類(lèi)是最重要的Java I18N類(lèi)。在Java語(yǔ)言中,幾乎所有的對(duì)國(guó)際化和本地化的支持都依賴于這個(gè)類(lèi)。如果Java類(lèi)庫(kù)中的某個(gè)類(lèi)在運(yùn)行的時(shí)候需要根據(jù)Locale對(duì)象來(lái)調(diào)整其功能,那么就稱(chēng)這個(gè)類(lèi)是本地敏感的(Locale-Sensitive), 例如java.text.DateFormat類(lèi)就是,依賴于特定Locale。

    創(chuàng)建Locale對(duì)象的時(shí)候,需要明確的指定其語(yǔ)言和國(guó)家的代碼,語(yǔ)言代碼遵從的是ISO-639規(guī)范,國(guó)家代碼遵從ISO-3166規(guī)范,可以從
    http://www.unicode.org/unicode/onlinedat/languages.html
    http://www.unicode.org/unicode/onlinedat/countries.htm

    Struts的國(guó)際化是基于properties的message/key對(duì)應(yīng)來(lái)實(shí)現(xiàn)的,筆者曾寫(xiě)過(guò)一個(gè)程序,所有Jsp頁(yè)面上沒(méi)有任何Text文本串,全部都用的是 <bean:message> 去Properties文件里面讀,這個(gè)時(shí)候其實(shí)只要指定不同的語(yǔ)言區(qū)域讀不同的Properties文件就實(shí)現(xiàn)了國(guó)際化。需要注意的是不同語(yǔ)言的字符寫(xiě)進(jìn)Properties文件的時(shí)候需要轉(zhuǎn)化成Unicode碼,JDK已經(jīng)帶有轉(zhuǎn)換的功能。JDK的bin目錄中有native2ascii這個(gè)命令,可以完成對(duì)*.txt和*.properties的Unicode碼轉(zhuǎn)換。


    這次準(zhǔn)備繼續(xù)上次的話題先講講Struts-2,手下簡(jiǎn)短回顧一段歷史:隨著時(shí)間的推移,Web應(yīng)用框架經(jīng)常變化的需求,產(chǎn)生了幾個(gè)下一代 Struts的解決方案。其中的Struts Ti 繼續(xù)堅(jiān)持 MVC模式的基礎(chǔ)上改進(jìn),繼續(xù)Struts的成功經(jīng)驗(yàn)。 WebWork項(xiàng)目是在2002年3月發(fā)布的,它對(duì)Struts式框架進(jìn)行了革命性改進(jìn),引進(jìn)了不少新的思想,概念和功能,但和原Struts代碼并不兼 容。WebWork是一個(gè)成熟的框架,經(jīng)過(guò)了好幾次重大的改進(jìn)與發(fā)布。在2005年12月,WebWork與Struts Ti決定合拼, 再此同時(shí), Struts Ti 改名為 Struts Action Framework 2.0,成為Struts真正的下一代。

    看看Struts-2的處理流程:
    1) Browser產(chǎn)生一個(gè)請(qǐng)求并提交框架來(lái)處理:根據(jù)配置決定使用哪些攔截器、action類(lèi)和結(jié)果等。
    2) 請(qǐng)求經(jīng)過(guò)一系列攔截器:根據(jù)請(qǐng)求的級(jí)別不同攔截器做不同的處理。這和Struts-1的RequestProcessor類(lèi)很相似。
    3) 調(diào)用Action: 產(chǎn)生一個(gè)新的action實(shí)例,調(diào)用業(yè)務(wù)邏輯方法。
    4) 調(diào)用產(chǎn)生結(jié)果:匹配result class并調(diào)用產(chǎn)生實(shí)例。
    5) 請(qǐng)求再次經(jīng)過(guò)一系列攔截器返回:過(guò)程也可配置減少攔截器數(shù)量
    6) 請(qǐng)求返回用戶:從control返回servlet,生成Html。

    這里很明顯的一點(diǎn)是不存在FormBean的作用域封裝,直接可以從Action中取得數(shù)據(jù)。 這里有一個(gè)Strut-2配置的web.xml文件:
    <filter>
    <filter-name> controller </filter-name>
    <filter-class> org.apache.struts.action2.dispatcher.FilterDispatcher </filter-class>
    </filter>
    <filter-mapping>
    <filter-name> cotroller </filter-name>
    <url-pattern> /* </url-pattern>
    </filter-mapping>

    注意到以往的servlet變成了filter,ActionServlet變成了FilterDispatcher,*.do變成了/*。filter 配置定義了名稱(chēng)(供關(guān)聯(lián))和filter的類(lèi)。filter mapping讓URI匹配成功的的請(qǐng)求調(diào)用該filter。默認(rèn)情況下,擴(kuò)展名為 ".action "。這個(gè)是在default.properties文件里的 "struts.action.extension "屬性定義的。

    default.properties是屬性定義文件,通過(guò)在項(xiàng)目classpath路徑中包含一個(gè)名為“struts.properties”的文件來(lái)設(shè)置不同的屬性值。而Struts-2的默認(rèn)配置文件名為struts.xml。由于1和2的action擴(kuò)展名分別為.do和.action,所以很方便能共存。我們?cè)賮?lái)看一個(gè)Struts-2的action代碼:
    public class MyAction {
    public String execute() throws Exception {
    //do the work
    return "success ";
    }
    }

    很明顯的區(qū)別是不用再繼承任何類(lèi)和接口,返回的只是一個(gè)String,無(wú)參數(shù)。實(shí)際上在Struts-2中任何返回String的無(wú)參數(shù)方法都可以通過(guò)配置來(lái)調(diào)用action。所有的參數(shù)從哪里來(lái)獲得呢?答案就是Inversion of Control技術(shù)(控制反轉(zhuǎn))。筆者盡量以最通俗的方式來(lái)解釋?zhuān)覀兿仍噲D讓這個(gè)Action獲得reuqest對(duì)象,這樣可以提取頁(yè)面提交的任何參數(shù)。那么我們把request設(shè)為一個(gè)成員變量,然后需要一個(gè)對(duì)它的set方法。由于大部分的action都需要這么做,我們把這個(gè)set方法作為接口來(lái)實(shí)現(xiàn)。
    public interface ServletRequestAware {
    public void setServletRequest(HttpServletRequest request);
    }

    public class MyAction implements ServletRequestAware {
    private HttpServletRequest request;

    public void setServletRequest(HttpServletRequest request) {
    this.request = request;
    }

    public String execute() throws Exception {
    // do the work directly using the request
    return Action.SUCCESS;
    }
    }

    那么誰(shuí)來(lái)調(diào)用這個(gè)set方法呢?也就是說(shuō)誰(shuí)來(lái)控制這個(gè)action的行為,以往我們都是自己在適當(dāng)?shù)牡胤綄?xiě)上一句 action.setServletRequest(…),也就是控制權(quán)在程序員這邊。然而控制反轉(zhuǎn)的思想是在哪里調(diào)用交給正在運(yùn)行的容器來(lái)決定,只要利用Java反射機(jī)制來(lái)獲得Method對(duì)象然后調(diào)用它的invoke方法傳入?yún)?shù)就能做到,這樣控制權(quán)就從程序員這邊轉(zhuǎn)移到了容器那邊。程序員可以減輕很多繁瑣的工作更多的關(guān)注業(yè)務(wù)邏輯。Request可以這樣注入到action中,其他任何對(duì)象也都可以。為了保證action的成員變量線程安全, Struts-2的action不是單例的,每一個(gè)新的請(qǐng)求都會(huì)產(chǎn)生一個(gè)新的action實(shí)例。

    那么有人會(huì)問(wèn),到底誰(shuí)來(lái)做這個(gè)對(duì)象的注入工作呢?答案就是攔截器。攔截器又是什么東西?筆者再來(lái)盡量通俗的解釋攔截器的概念。大家要理解攔截器的話,首先一定要理解GOF23種設(shè)計(jì)模式中的Proxy模式。

    A對(duì)象要調(diào)用f(),它希望代理給B來(lái)做,那么B就要獲得A對(duì)象的引用,然后在B的f()中通過(guò)A對(duì)象引用調(diào)用A對(duì)象的f()方法,最終達(dá)到A的f()被調(diào)用的目的。有沒(méi)有人會(huì)覺(jué)得這樣很麻煩,為什么明明只要A.f()就可以完成的一定要封裝到B的f()方法中去?有哪些好處呢?

    1) 這里我們只有一個(gè)A,當(dāng)我們有很多個(gè)A的時(shí)候,只需要監(jiān)視B一個(gè)對(duì)象的f()方法就可以從全局上控制所有被調(diào)用的f()方法。
    2) 另外,既然代理人B能獲得A對(duì)象的引用,那么B可以決定在真正調(diào)A對(duì)象的f()方法之前可以做哪些前置工作,調(diào)完返回前可有做哪些后置工作。

    講到這里,大家看出來(lái)一點(diǎn)攔截器的概念了么?它攔截下一調(diào)f()方法的請(qǐng)求,然后統(tǒng)一的做處理(處理每個(gè)的方式還可以不同,解析A對(duì)象就可以辨別),處理完畢再放行。這樣像不像對(duì)流動(dòng)的河水橫切了一刀,對(duì)所有想通過(guò)的水分子進(jìn)行搜身,然后再放行?這也就是AOP(Aspect of Programming面向切面編程)的思想。

    Anyway,Struts-2只是利用了AOP和IoC技術(shù)來(lái)減輕action和框架的耦合關(guān)系,力圖到最大程度重用action的目的。在這樣的技術(shù)促動(dòng)下,Struts-2的action成了一個(gè)簡(jiǎn)單被框架使用的POJO(Plain Old Java Object)罷了。實(shí)事上AOP和IoC的思想已經(jīng)遍布新出來(lái)的每一個(gè)框架上,他們并不是多么新的技術(shù),利用的也都是JDK早已可以最到的事情,它們代表的是更加面向接口編程,提高重用,增加擴(kuò)展性的一種思想。Struts-2只是部分的使用這兩種思想來(lái)設(shè)計(jì)完成的,另外一個(gè)最近很火的框架 Spring,更大程度上代表了這兩種設(shè)計(jì)思想,筆者將于下一篇來(lái)進(jìn)一步探討Spring的結(jié)構(gòu)。


    Spring
    引用《Spring2.0技術(shù)手冊(cè)》上的一段話:
    Spring的核心是個(gè)輕量級(jí)容器,它是實(shí)現(xiàn)IoC容器和非侵入性的框架,并提供AOP概念的實(shí)現(xiàn)方式;提供對(duì)持久層、事務(wù)的支持;提供MVC Web框架的實(shí)現(xiàn),并對(duì)于一些常用的企業(yè)服務(wù)API提供一致的模型封裝,是一個(gè)全方位的應(yīng)用程序框架,除此之外,對(duì)于現(xiàn)存的各種框架,Spring也提供了與它們相整合的方案。
    接下來(lái)筆者先談?wù)勛约旱囊恍├斫獍?,Spring框架的發(fā)起者之前一本很著名的書(shū)名字大概是《J2ee Development without EJB》,他提倡用輕量級(jí)的組件代替重量級(jí)的EJB。筆者還沒(méi)有看完那本著作,只閱讀了部分章節(jié)。其中有一點(diǎn)分析覺(jué)得是很有道理的:

    EJB里在服務(wù)器端有Web Container和EJB Container,從前的觀點(diǎn)是各層之間應(yīng)該在物理上隔離,Web Container處理視圖功能、在EJB Container中處理業(yè)務(wù)邏輯功能、然后也是EBJ Container控制數(shù)據(jù)庫(kù)持久化。這樣的層次是很清晰,但是一個(gè)很?chē)?yán)重的問(wèn)題是Web Container和EJB Container畢竟是兩個(gè)不同的容器,它們之間要通信就得用的是RMI機(jī)制和JNDI服務(wù),同樣都在服務(wù)端,卻物理上隔離,而且每次業(yè)務(wù)請(qǐng)求都要遠(yuǎn)程調(diào)用,有沒(méi)有必要呢?看來(lái)并非隔離都是好的。

    再看看輕量級(jí)和重量級(jí)的區(qū)別,筆者看過(guò)很多種說(shuō)法,覺(jué)得最有道理的是輕量級(jí)代表是POJO + IoC,重量級(jí)的代表是Container + Factory。(EJB2.0是典型的重量級(jí)組件的技術(shù))我們盡量使用輕量級(jí)的Pojo很好理解,意義就在于兼容性和可適應(yīng)性,移植不需要改變?cè)瓉?lái)的代碼。而Ioc與Factory比起來(lái),Ioc的優(yōu)點(diǎn)是更大的靈活性,通過(guò)配置可以控制很多注入的細(xì)節(jié),而Factory模式,行為是相對(duì)比較封閉固定的,生產(chǎn)一個(gè)對(duì)象就必須接受它全部的特點(diǎn),不管是否需要。其實(shí)輕量級(jí)和重量級(jí)都是相對(duì)的概念,使用資源更少、運(yùn)行負(fù)載更小的自然就算輕量。

    話題扯遠(yuǎn)了,因?yàn)镾pring框架帶來(lái)了太多可以探討的地方。比如它的非侵入性:指的是它提供的框架實(shí)現(xiàn)可以讓程序員編程卻感覺(jué)不到框架的存在,這樣所寫(xiě)的代碼并沒(méi)有和框架綁定在一起,可以隨時(shí)抽離出來(lái),這也是Spring設(shè)計(jì)的目標(biāo)。Spring是唯一可以做到真正的針對(duì)接口編程,處處都是接口,不依賴綁定任何實(shí)現(xiàn)類(lèi)。同時(shí),Spring還設(shè)計(jì)了自己的事務(wù)管理、對(duì)象管理和Model2 的MVC框架,還封裝了其他J2ee的服務(wù)在里面,在實(shí)現(xiàn)上基本都在使用依賴注入和AOP的思想。由此我們大概可以看到Spring是一個(gè)什么概念上的框架,代表了很多優(yōu)秀思想,值得深入學(xué)習(xí)。筆者強(qiáng)調(diào),學(xué)習(xí)并不是框架,而是框架代表的思想,就像我們當(dāng)初學(xué)Struts一樣……

    1.Spring MVC
    關(guān)于IoC和AOP筆者在上篇已經(jīng)稍微解釋過(guò)了,這里先通過(guò)Spring的MVC框架來(lái)給大家探討一下Spring的特點(diǎn)吧。(畢竟大部分人已經(jīng)很熟悉Struts了,對(duì)比一下吧)
    眾所周知MVC的核心是控制器。類(lèi)似Struts中的ActionServlet,Spring里面前端控制器叫做DispatcherServlet。里面充當(dāng)Action的組件叫做Controller,返回的視圖層對(duì)象叫做ModelAndView,提交和返回都可能要經(jīng)過(guò)過(guò)濾的組件叫做 Interceptor。

    讓我們看看一個(gè)從請(qǐng)求到返回的流程吧:
    (1) 前臺(tái)Jsp或Html通過(guò)點(diǎn)擊submit,將數(shù)據(jù)裝入了request域
    (2) 請(qǐng)求被Interceptor攔截下來(lái),執(zhí)行preHandler()方法出前置判斷
    (3) 請(qǐng)求到達(dá)DispathcerServlet
    (4) DispathcerServlet通過(guò)Handler Mapping來(lái)決定每個(gè)reuqest應(yīng)該轉(zhuǎn)發(fā)給哪個(gè)后端控制器Controlle


    ORM
    其實(shí)J2ee的規(guī)范指南里面就已經(jīng)包括了一些對(duì)象持久化技術(shù),例如JDO(Java Data Object)就是Java對(duì)象持久化的新規(guī)范,一個(gè)用于存取某種數(shù)據(jù)倉(cāng)庫(kù)中的對(duì)象的標(biāo)準(zhǔn)化API,提供了透明的對(duì)象存儲(chǔ),對(duì)開(kāi)發(fā)人員來(lái)說(shuō),存儲(chǔ)數(shù)據(jù)對(duì)象完全不需要額外的代碼(如JDBC API的使用)。這些繁瑣的工作已經(jīng)轉(zhuǎn)移到JDO產(chǎn)品提供商身上,使開(kāi)發(fā)人員解脫出來(lái),從而集中時(shí)間和精力在業(yè)務(wù)邏輯上。另外,JDO很靈活,因?yàn)樗梢栽谌魏螖?shù)據(jù)底層上運(yùn)行。JDBC只是面向關(guān)系數(shù)據(jù)庫(kù)(RDBMS)JDO更通用,提供到任何數(shù)據(jù)底層的存儲(chǔ)功能,比如關(guān)系數(shù)據(jù)庫(kù)、文件、XML以及對(duì)象數(shù)據(jù)庫(kù)(ODBMS)等等,使得應(yīng)用可移植性更強(qiáng)。我們?nèi)绻斫鈱?duì)象持久化技術(shù),首先要問(wèn)自己一個(gè)問(wèn)題:為什么傳統(tǒng)的JDBC來(lái)持久化不再能滿足大家的需求了呢?

    筆者認(rèn)為最好是能用JDBC真正編寫(xiě)過(guò)程序了才能真正體會(huì)ORM的好處,同樣的道理,真正拿Servlet/Jsp做過(guò)項(xiàng)目了才能體會(huì)到Struts、 Spring等框架的方便之處。很幸運(yùn)的是筆者這兩者都曾經(jīng)經(jīng)歷過(guò),用混亂的內(nèi)嵌Java代碼的Jsp加Servlet轉(zhuǎn)發(fā)寫(xiě)過(guò)完整的Web項(xiàng)目,也用 JDBC搭建過(guò)一個(gè)完整C/S項(xiàng)目的后臺(tái)。所以現(xiàn)在接觸到新框架才更能體會(huì)它們思想和實(shí)現(xiàn)的優(yōu)越之處,回顧從前的代碼,真是丑陋不堪啊。^_^

    回到正題,我們來(lái)研究一下為什么要從JDBC發(fā)展到ORM。簡(jiǎn)單來(lái)說(shuō),傳統(tǒng)的JDBC要花大量的重復(fù)代碼在初始化數(shù)據(jù)庫(kù)連接上,每次增刪改查都要獲得 Connection對(duì)象,初始化Statement,執(zhí)行得到ResultSet再封裝成自己的List或者Object,這樣造成了在每個(gè)數(shù)據(jù)訪問(wèn)方法中都含有大量冗余重復(fù)的代碼,考慮到安全性的話,還要加上大量的事務(wù)控制和log記錄。雖然我們學(xué)習(xí)了設(shè)計(jì)模式之后,可以自己定義Factory來(lái)幫助減少一部分重復(fù)的代碼,但是仍然無(wú)法避免冗余的問(wèn)題。其次,隨著OO思想深入人心,連典型的過(guò)程化語(yǔ)言Perl等都冠冕堂皇的加上了OO的外殼,何況是 Java中繁雜的數(shù)據(jù)庫(kù)訪問(wèn)持久化技術(shù)呢?強(qiáng)調(diào)面向?qū)ο缶幊痰慕Y(jié)果就是找到一個(gè)橋梁,使得關(guān)系型數(shù)據(jù)庫(kù)存儲(chǔ)的數(shù)據(jù)能準(zhǔn)確的映射到Java的對(duì)象上,然后針對(duì)Java對(duì)象來(lái)設(shè)計(jì)對(duì)象和方法,如果我們把數(shù)據(jù)庫(kù)的Table當(dāng)作Class,Record當(dāng)作Instance的話,就可以完全用面向?qū)ο蟮乃枷雭?lái)編寫(xiě)數(shù)據(jù)層的代碼。于是乎,Object Relationship Mapping的概念開(kāi)始普遍受到重視,盡管很早很早就已經(jīng)有人提出來(lái)了。
    缺點(diǎn)我們已經(jīng)大概清楚了,那么如何改進(jìn)呢?對(duì)癥下藥,首先我們要解決的是如何從Data Schema準(zhǔn)備完美的映射到Object Schema,另外要提供對(duì)數(shù)據(jù)庫(kù)連接對(duì)象生命周期的管理,對(duì)事務(wù)不同粒度的控制和考慮到擴(kuò)展性后提供對(duì)XML、Properties等可配置化的文件的支持。到目前為止,有很多框架和技術(shù)在嘗試著這樣做。例如似乎是封裝管理得過(guò)了頭的EJB、很早就出現(xiàn)目前已經(jīng)不在開(kāi)發(fā)和升級(jí)了的Apache OJB、首先支持Manual SQL的iBATIS,還有公認(rèn)非常優(yōu)秀的Hibernate等等。在分別介紹它們之前,我還想反復(fù)強(qiáng)調(diào)這些框架都在試圖做什么:

    畢竟Java Object和數(shù)據(jù)庫(kù)的每一條Record還是有很大的區(qū)別,就是類(lèi)型上來(lái)說(shuō),DB是沒(méi)有Boolean類(lèi)型的。而Java也不得不用封裝類(lèi)(Integer、Double等)為了能映射上數(shù)據(jù)庫(kù)中為null的情況,畢竟Primitive類(lèi)型是沒(méi)有null值的。還有一個(gè)比較明顯的問(wèn)題是,數(shù)據(jù)庫(kù)有主鍵和外鍵,而Java中仍然只能通過(guò)基本類(lèi)型來(lái)對(duì)應(yīng)字段值而已,無(wú)法規(guī)定Unique等特征,更別提外鍵約束、事務(wù)控制和級(jí)聯(lián)操作了。另外,通過(guò)Java Object預(yù)設(shè)某Field值去取數(shù)據(jù)庫(kù)記錄,是否在這樣的記錄也是不能保證的。真的要設(shè)計(jì)到完全映射的話,Java的Static被所有對(duì)象共享的變量怎么辦?在數(shù)據(jù)庫(kù)中如何表現(xiàn)出來(lái)……
    我們能看到大量的問(wèn)題像一座座大山橫在那些框架設(shè)計(jì)者們面前,他們并不是沒(méi)有解決辦法,而是從不同的角度去考慮,會(huì)得到很多不同的解決方案,問(wèn)題是應(yīng)該采取哪一種呢?甚至只有等到真正設(shè)計(jì)出來(lái)了投入生產(chǎn)使用了,才能印證出當(dāng)初的設(shè)想是否真的能為項(xiàng)目開(kāi)發(fā)帶來(lái)更多的益處。筆者引用一份文檔中提到一個(gè)健壯的持久化框架應(yīng)該具有的特點(diǎn):
    A robust persistence layer should support----
    1. Several types of persistence mechanism
    2. Full encapsulation of the persistence mechanism.
    3. Multi-object actions
    4. Transactions Control
    5. Extensibility
    6. Object identifiers
    7. Cursors: logical connection to the persistence mechanism
    8. Proxies: commonly used when the results of a query are to be displayed in a list
    9. Records: avoid the overhead of converting database records to objects and then back to records
    10. Multi architecture
    11. Various database version and/or vendors
    12. Multiple connections
    13. Native and non-native drivers
    14. Structured query language queries(SQL)

    現(xiàn)在來(lái)簡(jiǎn)短的介紹一下筆者用過(guò)的一些持久化框架和技術(shù),之所以前面強(qiáng)調(diào)那么多共通的知識(shí),是希望大家不要盲從流行框架,一定要把握它的本質(zhì)和卓越的思想好在哪里。
    1. Apache OJB
    OJB代表Apache Object Relational Bridge,是Apache開(kāi)發(fā)的一個(gè)數(shù)據(jù)庫(kù)持久型框架。它是基于J2ee規(guī)范指南下的持久型框架技術(shù)而設(shè)計(jì)開(kāi)發(fā)的,例如實(shí)現(xiàn)了ODMG 3.0規(guī)范的API,實(shí)現(xiàn)了JDO規(guī)范的API, 核心實(shí)現(xiàn)是Persistence Broker API。OJB使用XML文件來(lái)實(shí)現(xiàn)映射并動(dòng)態(tài)的在Metadata layer聽(tīng)過(guò)一個(gè)Meta-Object-Protocol(MOP)來(lái)改變底層數(shù)據(jù)的行為。更高級(jí)的特點(diǎn)包括對(duì)象緩存機(jī)制、鎖管理機(jī)制、 Virtual 代理、事務(wù)隔離性級(jí)別等等。舉個(gè)OJB Mapping的簡(jiǎn)單例子ojb-repository.xml:

    <class-descriptor class=”com.ant.Employee” table=”EMPLOYEE”>
    <field-descriptor name=”id” column=”ID”
    jdbc-type=”INTEGER” primarykey=”true” autoincrement=”true”/>

    <field-descriptor name=”name” column=”NAME” jdbc-type=”VARCHAR”/>
    </class-descrptor>

    <class-descriptor class=”com.ant.Executive” table=”EXECUTIVE”>
    <field-descriptor name=”id” column=”ID”
    jdbc-type=”INTEGER” primarykey=”true” autoincrement=”true”/>

    <field-descriptor name=”department” column=”DEPARTMENT” jdbc-type=”VARCHAR”/>

    <reference-descriptor name=”super” class-ref=”com.ant.Employee”>
    <foreignkey field-ref=”id”/>
    </reference-descriptor>
    </class-descrptor>

    2. iBATIS
    iBATIS最大的特點(diǎn)就是允許用戶自己定義SQL來(lái)組配Bean的屬性。因?yàn)樗腟QL語(yǔ)句是直接寫(xiě)入XML文件中去的,所以可以最大程度上利用到 SQL語(yǔ)法本身能控制的全部特性,同時(shí)也能允許你使用特定數(shù)據(jù)庫(kù)服務(wù)器的額外特性,并不局限于類(lèi)似SQL92這樣的標(biāo)準(zhǔn),它最大的缺點(diǎn)是不支持枚舉類(lèi)型的持久化,即把枚舉類(lèi)型的幾個(gè)對(duì)象屬性拼成與數(shù)據(jù)庫(kù)一個(gè)字段例如VARCHAR對(duì)應(yīng)的行為。這里也舉一個(gè)Mapping文件的例子sqlMap.xml:
    <sqlMap>
    <typeAlias type=”com.ant.Test” alias=”test”/>

    <resultMap class=”test” id=”result”>
    <result property=”testId” column=”TestId”/>
    <result property=”name” column=”Name”/>
    <result property=”date” column=”Date”/>
    </resultMap>

    <select id=”getTestById” resultMap=”result” parameterClass=”int”>
    select * from Test where TestId=#value#
    </select>

    <update id=”updateTest” parameterClass=”test”>
    Update Tests set Name=#name#, Date=”date” where TestId=#testId#
    </update>
    </sqlMap>

    3. Hibernate
    Hibernate無(wú)疑是應(yīng)用最廣泛最受歡迎的持久型框架,它生成的SQL語(yǔ)句是非常優(yōu)秀。雖然一度因?yàn)椴荒苤С质止QL而性能受到局限,但隨著新一代 Hibernate 3.x推出,很多缺點(diǎn)都被改進(jìn),Hibernate也因此變得更加通用而時(shí)尚。同樣先看一個(gè)Mapping文件的例子customer.hbm.xml來(lái)有一個(gè)大概印象:

    <hibernate-mapping>
    <class name=”com.ant.Customer” table=”Customers”>
    <id name=”customerId” column=”CustomerId” type=”int” unsaved-value=”0”>
    <generator class=”sequence”>
    <param name=”sequence”> Customers_CustomerId_Seq </param>
    </generator>
    </id>

    <property name=”firstName” column=”FirstName”/>
    <property name=”lastName” column=”LastName”/>

    <set name=”addresses” outer-join=”true”>
    <key column=”Customer”/>
    <one-to-many class=”com.ant.Address”/>
    </set>
    </class>
    </hibernate-mapping>

    Hibernate有很多顯著的特性,最突出的就是它有自己的查詢語(yǔ)言叫做HQL,在HQL中select from的不是Table而是類(lèi)名,一方面更加面向?qū)ο螅硗庖环矫嫱ㄟ^(guò)在hibernate.cfg.xml中配置Dialect為HQL可以使得整個(gè)后臺(tái)與數(shù)據(jù)庫(kù)脫離耦合,因?yàn)椴还苡媚欠N數(shù)據(jù)庫(kù)我都是基于HQL來(lái)查詢,Hibernate框架負(fù)責(zé)幫我最終轉(zhuǎn)換成特定數(shù)據(jù)庫(kù)里的SQL語(yǔ)句。另外 Hibernate在Object-Caching這方面也做得相當(dāng)出色,它同時(shí)管理兩個(gè)級(jí)別的緩存,當(dāng)數(shù)據(jù)被第一次取出后,真正使用的時(shí)候?qū)ο蟊环旁谝患?jí)緩存管理,這個(gè)時(shí)候任何改動(dòng)都會(huì)影響到數(shù)據(jù)庫(kù);而空閑時(shí)候會(huì)把對(duì)象放在二級(jí)緩存管理,雖然這個(gè)時(shí)候與數(shù)據(jù)庫(kù)字段能對(duì)應(yīng)上但未綁定在一起,改動(dòng)不會(huì)影響到數(shù)據(jù)庫(kù)的記錄,主要目的是為了在重復(fù)讀取的時(shí)候更快的拿到數(shù)據(jù)而不用再次請(qǐng)求連接對(duì)象。其實(shí)關(guān)于這種緩存的設(shè)計(jì)建議大家研究一下Oracle的存儲(chǔ)機(jī)制(原理是相通的),Oracle犧牲了空間換來(lái)時(shí)間依賴于很健壯的緩存算法來(lái)保證最優(yōu)的企業(yè)級(jí)數(shù)據(jù)庫(kù)訪問(wèn)速率。

    以上是一些Mapping的例子,真正在Java代碼中使用多半是繼承各個(gè)框架中默認(rèn)的Dao實(shí)現(xiàn)類(lèi),然后可以通過(guò)Id來(lái)查找對(duì)象,或者通過(guò) Example來(lái)查找,更流行的是更具Criteria查找對(duì)象。Criteria是完全封裝了SQL條件查詢語(yǔ)法的一個(gè)工具類(lèi),任何一個(gè)查詢條件都可以在Criteria中找到方法與之對(duì)應(yīng),這樣可以在Java代碼級(jí)別實(shí)現(xiàn)SQL的完全控制。另外,現(xiàn)在許多ORM框架的最新版本隨著JDk 5.0加入Annotation特性都開(kāi)始支持用XDoclet來(lái)自動(dòng)根據(jù)Annotation來(lái)生成XML配置文件了。




    Author: orangelizq
    email: orangelizq@163.com

    歡迎大家訪問(wèn)我的個(gè)人網(wǎng)站 萌萌的IT人
    posted on 2009-08-27 17:09 桔子汁 閱讀(407) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): J2EE
    主站蜘蛛池模板: 亚洲国产视频久久| 亚洲av无码不卡久久| 亚洲国产美国国产综合一区二区| 亚洲va久久久噜噜噜久久狠狠 | 中文字幕亚洲综合精品一区| 中文字幕在线观看亚洲视频| 特级毛片aaaa级毛片免费| 四虎影视无码永久免费| 4hu四虎最新免费地址| 国产精品成人无码免费| 国产午夜亚洲精品午夜鲁丝片| 日产亚洲一区二区三区| 亚洲狠狠色丁香婷婷综合| 国产在线精品一区免费香蕉| 国产高清免费视频| 国产三级免费电影| 亚洲bt加勒比一区二区| 亚洲AV成人无码网站| 国产成人AV免费观看| 大陆一级毛片免费视频观看i| 久久久久亚洲精品中文字幕| 亚洲国产av美女网站| 免费观看四虎精品成人| 久久久久久精品成人免费图片 | 亚洲av午夜福利精品一区| 亚洲日产乱码一二三区别| 久久精品成人免费国产片小草 | 免费精品99久久国产综合精品| 免费国产成人高清在线观看网站| 亚洲国产精品一区二区第四页| 亚洲综合在线视频| 特级毛片全部免费播放a一级| 曰曰鲁夜夜免费播放视频| 久久久久亚洲爆乳少妇无| 久久亚洲精品国产亚洲老地址| a级毛片免费完整视频| 成人性生交大片免费看午夜a| 亚洲国产精品成人精品无码区| 亚洲av无码专区亚洲av不卡| 亚洲视频在线观看免费| 免费成人av电影|