原文:http://home.phpchina.com/space.php?uid=108994&do=blog&id=69516
接口& 組件、容器
1. J2ee與接口
每一個版本的J2ee都對應著一個確定版本的JDK,J2ee1.4對應Jdk1.4,現在比較新的是JDK5.0,自然也會有J2EE
5.0。其實筆者一直在用的是J2EE1.4,不過沒什么關系,大家可以下任何一個版本的J2ee
api來稍微瀏覽一下。筆者想先聲明一個概念,J2ee也是源自Java,所以底層的操作依然調用到很多J2se的庫,所以才建議大家先牢牢掌握J2se 的主流技術。
J2ee
api有一個特點,大家比較熟悉的幾個包java.jms、javax.servlet.http、javax.ejb等都以interface居多,實現類較少。其實大家真正在用的時候百分之六十以上都在反復的查著javax.servlet.http這個包下面幾個實現類的api函數,其他的包很少問津。筆者建議在學習一種技術之前,對整體的框架有一個了解是很有必要的,J2ee旨在通過interface的聲明來規范實現的行為,任何第三方的廠商想要提供自己品牌的實現前提也是遵循這些接口定義的規則。如果在從前J2se學習的道路上對接口的理解很好的話,這里的體會將是非常深刻的,舉個簡單的例子:
public interface Mp3{
public void play();
public void record();
public void stop();
}
如果我定義這個簡單的接口,發布出去,規定任何第三方的公司想推出自己的名字為Mp3的產品都必須實現這個接口,也就是至少提供接口中方法的具體實現。這個意義已經遠遠不止是面向對象的多態了,只有廠商遵循J2ee的接口定義,世界上的J2ee程序員才能針對統一的接口進行程序設計,最終不用改變代碼只是因為使用了不同廠商的實現類而有不同的特性罷了,本質上說,無論哪一種廠商實現都完成了職責范圍內的工作。這個就是筆者想一直強調的,針對接口編程的思想。
接口到底有什么好處呢?我們這樣設想,現在有AppleMp3、SonyMp3、SamsungMp3都實現了這個Mp3的接口,于是都有了play、
record、stop這三個功能。我們將Mp3產品座位一個組件的時候就不需要知道它的具體實現,只要看到接口定義知道這個對象有3個功能就可以使用了。那么類似下面這樣的業務就完全可以在任何時間從3個品牌擴展到任意個品牌,開個玩笑的說,項目經理高高在上的寫完10個接口里的方法聲明,然后就丟給手下的程序員去寫里面的細節,由于接口已經統一(即每個方法傳入和傳出的格式已經統一),經理只需關注全局的業務就可以天天端杯咖啡走來走去了,^_^:
public Mp3 create();
public void copy(Mp3 mp3);
public Mp3 getMp3();
最后用一個簡單的例子說明接口:一個5號電池的手電筒,可以裝入任何牌子的5號電池,只要它符合5號電池的規范,裝入之后任何看不到是什么牌子,只能感受到手電筒在完成它的功能。那么生產手電筒的廠商和生產5號電池的廠商就可以完全解除依賴關系,可以各自自由開發自己的產品,因為它們都遵守5號電池應有的形狀、正負極位置等約定。這下大家能對接口多一點體會了么?
2. 組件和容器
針對接口是筆者特意強調的J2ee學習之路必備的思想,另外一個就是比較常規的組件和容器的概念了。很多教材和專業網站都說J2EE的核心是一組規范與指南,強調J2ee的核心概念就是組件+容器,這確實是無可厚非的。隨著越來越多的J2ee框架出現,相應的每種框架都一般有與之對應的容器。
容器,是用來管理組件行為的一個集合工具,組件的行為包括與外部環境的交互、組件的生命周期、組件之間的合作依賴關系等等。J2ee包含的容器種類大約有
Web容器、Application
Client容器、EJB容器、Applet客戶端容器等。但在筆者看來,現在容器的概念變得有點模糊了,大家耳熟能詳是那些功能強大的開源框架,比如
Hibernate、Struts2、Spring、JSF等,其中Hibernate就基于JDBC的基礎封裝了對事務和會話的管理,大大方便了對數據庫操作的繁瑣代碼,從這個意義上來說它已經接近容器的概念了,EJB的實體Bean也逐漸被以Hibernate為代表的持久化框架所取代。
組件,本意是指可以重用的代碼單元,一般代表著一個或者一組可以獨立出來的功能模塊,在J2ee中組件的種類有很多種,比較常見的是EJB組件、DAO組件、客戶端組件或者應用程序組件等,它們有個共同特點是分別會打包成.war,.jar,.jar,.ear,每個組件由特定格式的xml描述符文件進行描述,而且服務器端的組件都需要被部署到應用服務器上面才能夠被使用。
稍微理解完組件和容器,還有一個重要的概念就是分層模型,最著名的當然是MVC三層模型。在一個大的工程或項目中,為了讓前臺和后臺各個模塊的編程人員能夠同時進行工作提高開發效率,最重要的就是實現層與層之間的耦合關系,許多分層模型的宗旨和開源框架所追求的也就是這樣的效果。在筆者看來,一個完整的
Web項目大概有以下幾個層次:
a) 表示層(Jsp、Html、Javascript、Ajax、Flash等等技術對其支持)
b)
控制層(Struts、JSF、WebWork等等框架在基于Servlet的基礎上支持,負責把具體的請求數據(有時卸載重新裝載)導向適合處理它的模型層對象)
c) 模型層(筆者認為目前最好的框架是Spring,實質就是處理表示層經由控制層轉發過來的數據,包含著大量的業務邏輯)
d)
數據層(Hibernate、JDBC、EJB等,由模型層處理完了持久化到數據庫中)
Servlet/Jsp
終于正式進入J2ee的細節部分了,首當其沖的當然是Servlet和Jsp了,上篇曾經提到過J2ee只是一
個規范和指南,定義了一組必須要遵循的接口,核心概念是組件和容器。曾經有的人問筆者Servlet的Class文件是哪里來的?他認為是J2ee官方提
供的,我舉了一個簡單的反例:稍微檢查了一下Tomcat5.0里面的Servlet.jar文件和JBoss里面的Servlet.jar文件大小,很
明顯是不一樣的,至少已經說明了它們不是源自同根的吧。其實Servlet是由容器根據J2ee的接口定義自己來實現的,實現的方式當然可以不同,只要都
遵守J2ee規范和指南。
上述只是一個常見的誤區罷了,告訴我們要編譯運行Servlet,是要依賴于實現它的容器的,不然連jar文件都沒有,編譯都無法進
行。那么Jsp呢? Java Server
Page的簡稱,是為了開發動態網頁而誕生的技術,其本質也是Jsp,在編寫完畢之后會在容器啟動時經過編譯成對應的Servlet。只是我們利用Jsp
的很多新特性,可以更加專注于前后臺的分離,早期Jsp做前臺是滿流行的,畢竟里面支持Html代碼,這讓前臺美工人員可以更有效率的去完成自己的工作。
然后Jsp將請求轉發到后臺的Servlet,由Servlet處理業務邏輯,再轉發回另外一個Jsp在前臺顯示出來。這似乎已經成為一種常用的模式,最
初筆者學習J2ee的時候,大量時間也在編寫這樣的代碼。
盡管現在做前臺的技術越來越多,例如Flash、Ajax等,已經有很多人不再認為Jsp重要了。筆者覺得Jsp帶來的不僅僅是前后
端分離的設計理念,它的另外一項技術成就了我們今天用的很多框架,那就是Tag標簽技術。所以與其說是在學習Jsp,不如更清醒的告訴自己在不斷的理解
Tag標簽的意義和本質。
1. Servlet以及Jsp的生命周期
Servlet是Jsp的實質,盡管容器對它們的處理有所區別。Servlet有init()方法初始化,service()方法進行Web服
務,
destroy()方法進行銷毀,從生到滅都由容器來掌握,所以這些方法除非你想自己來實現Servlet,否則是很少會接觸到的。正是由于很少接觸,才
容易被廣大初學者所忽略,希望大家至少記住Servlet生命周期方法都是回調方法。回調這個概念簡單來說就是把自己注入另外一個類中,由它來調用你的方
法,所謂的另外一個類就是Web容器,它只認識接口和接口的方法,注入進來的是怎樣的對象不管,它只會根據所需調用這個對象在接口定義存在的那些方法。由
容器來調用的Servlet對象的初始化、服務和銷毀方法,所以叫做回調。這個概念對學習其他J2ee技術相當關鍵!
那么Jsp呢?本事上是Servlet,還是有些區別的,它的生命周期是這樣的:
a) 一個客戶端的Request到達服務器 ->
b) 判斷是否第一次調用 -> 是的話編譯Jsp成Servlet
c) 否的話再判斷此Jsp是否有改變 ->
是的話也重新編譯Jsp成Servlet
d) 已經編譯最近版本的Servlet裝載所需的其他Class
e)
發布Servlet,即調用它的Service()方法
所以Jsp號稱的是第一次Load緩慢,以后都會很快的運行。從它的生命的周期確實不難看出來這個特點,客戶端的操作很少會改變Jsp的
源碼,所以它不需要編譯第二次就一直可以為客戶端提供服務。這里稍微解釋一下Http的無狀態性,因為發現很多人誤解,Http的無狀態性是指每次一張頁
面顯示出來了,與服務器的連接其實就已經斷開了,當再次有提交動作的時候,才會再次與服務器進行連接請求提供服務。當然還有現在比較流行的是Ajax與服
務器異步通過 xml交互的技術,在做前臺的領域潛力巨大,筆者不是Ajax的高手,這里無法為大家解釋。
2. Tag標簽的本質
筆者之前說了,Jsp本身初衷是使得Web應用前后臺的開發可以脫離耦合分開有效的進行,可惜這個理念的貢獻反倒不如它帶來的Tag技術對
J2ee的貢獻要大。也許已經有很多人開始使用Tag技術了卻并不了解它。所以才建議大家在學習J2ee開始的時候一定要認真學習Jsp,其實最重要的就
是明白標簽的本質。
Html標簽我們都很熟悉了,有 <html> 、 <head> 、 <body> 、
<title> ,Jsp帶來的Tag標簽遵循同樣的格式,或者說更嚴格的Xml格式規范,例如 <jsp:include> 、
<jsp:useBean> 、 <c:if> 、 <c:forEach>
等等。它們沒有什么神秘的地方,就其源頭也還是Java
Class而已,Tag標簽的實質也就是一段Java代碼,或者說一個Class文件。當配置文件設置好去哪里尋找這些Class的路徑后,容器負責將頁面中存在的標簽對應到相應的Class上,執行那段特定的Java代碼,如此而已。
說得明白一點的話還是舉幾個簡單的例子說明一下吧:
<jsp:include>
去哪里找執行什么class呢?首先這是個jsp類庫的標簽,當然要去jsp類庫尋找相應的class了,同樣它也是由Web容器來提供,例如
Tomcat就應該去安裝目錄的lib文件夾下面的jsp-api.jar里面找,有興趣的可以去找一找啊!
<c:forEach>
又去哪里找呢?這個是由Jsp2.0版本推薦的和核心標記庫的內容,例如 <c:if>
就對應在頁面中做if判斷的功能的一斷Java代碼。它的class文件在jstl.jar這個類庫里面,往往還需要和一個standard.jar類庫一起導入,放在具體Web項目的WEB-INF的lib目錄下面就可以使用了。
順便羅唆一句,Web
Project的目錄結構是相對固定的,因為容器會按照固定的路徑去尋找它需要的配置文件和資源,這個任何一本J2ee入門書上都有,這里就不介紹了。了
解Tag的本質還要了解它的工作原理,所以大家去J2ee的API里找到并研究這個包:javax.servlet.jsp.tagext。它有一些接
口,和一些實現類,專門用語開發Tag,只有自己親自寫出幾個不同功能的標簽,才算是真正理解了標簽的原理。別忘記了自己開發的標簽要自己去完成配置文
件,容器只是集成了去哪里尋找jsp標簽對應class的路徑,自己寫的標簽庫當然要告訴容器去哪里找啦。
說了這么多,我們為什么要用標簽呢?完全在Jsp里面來個 <% %>
就可以在里面任意寫Java代碼了,但是長期實踐發現頁面代碼統一都是與html同風格的標記語言更加有助于美工人員進行開發前臺,它不需要懂Java,
只要Java程序員給個列表告訴美工什么標簽可以完成什么邏輯功能,他就可以專注于美工,也算是進一步隔離了前后臺的工作吧!
3. 成就Web框架
框架是什么?曾經看過這樣的定義:與模式類似,框架也是解決特定問題的可重用方法,框架是一個描述性的構建塊和服務集合,開發人員可以用來達成某個目標。一般來說,框架提供了解決某類問題的基礎設施,是用來創建解決方案的工具,而不是問題的解決方案。
正是由于Tag的出現,成就了以后出現的那么多Web框架,它們都開發了自己成熟實用的一套標簽,然后由特定的Xml文件來配置加載信息,力圖使得Web
應用的開發變得更加高效。下面這些標簽相應對很多人來說相當熟悉了:
<html:password>
<logic:equal>
<bean:write>
<f:view>
<h:form>
<h:message>
它們分別來自Struts和JSF框架,最強大的功能在于控制轉發,就是MVC三層模型中間完成控制器的工作。Struts-1實際上并未做到真正的三層隔離,這一點在Struts-2上得到了很大的改進。而Jsf向來以比較完善合理的標簽庫受到人們推崇。
今天就大概講這么多吧,再次需要強調的是Servlet/Jsp是學習J2ee必經之路,也是最基礎的知識,希望大家給與足夠的重視!
Struts
J2ee的開源框架很多,筆者只能介紹自己熟悉的幾個,其他的目前在中國IT行業應用得不是很多。希望大家對新出的框架不要盲目的推崇,首先一定要熟悉它比舊的到底好在哪里,新的理念和特性是什么?然后再決定是否要使用它。
這期的主題是Struts,直譯過來是支架。Struts的第一個版本是在2001年5月發布的,它提供了一個Web應用的解決方案,如何讓Jsp和
servlet共存去提供清晰的分離視圖和業務應用邏輯的架構。在Struts之前,通常的做法是在Jsp中加入業務邏輯,或者在Servlet中生成視圖轉發到前臺去。Struts帶著MVC的新理念當時退出幾乎成為業界公認的Web應用標準,于是當代IT市場上也出現了眾多熟悉Struts的程序員。即使有新的框架再出來不用,而繼續用Struts的理由也加上了一條低風險,因為中途如果開發人員變動,很容易的招進新的會Struts的IT民工啊,
^_^!
筆者之前說的都是Struts-1,因為新出了Struts-2,使得每次談到Struts都必須注明它是Struts-1還是2。筆者先談比較熟悉的
Struts-1,下次再介紹一下與Struts-2的區別:
1. Struts框架整體結構
Struts-1的核心功能是前端控制器,程序員需要關注的是后端控制器。前端控制器是是一個Servlet,在Web.xml中間配置所有
Request都必須經過前端控制器,它的名字是ActionServlet,由框架來實現和管理。所有的視圖和業務邏輯隔離都是應為這個 ActionServlet,
它就像一個交通警察,所有過往的車輛必須經過它的法眼,然后被送往特定的通道。所有,對它的理解就是分發器,我們也可以叫做Dispatcher,其實了解Servlet編程的人自己也可以寫一個分發器,加上攔截request的Filter,其實自己實現一個struts框架并不是很困難。主要目的就是讓編寫視圖的和后臺邏輯的可以脫離緊耦合,各自同步的完成自己的工作。
那么有了ActionServlet在中間負責轉發,前端的視圖比如說是Jsp,只需要把所有的數據Submit,這些數據就會到達適合處理它的后端控制器Action,然后在里面進行處理,處理完畢之后轉發到前臺的同一個或者不同的視圖Jsp中間,返回前臺利用的也是Servlet里面的forward
和redirect兩種方式。所以到目前為止,一切都只是借用了Servlet的API搭建起了一個方便的框架而已。這也是Struts最顯著的特性?? 控制器。
那么另外一個特性,可以說也是Struts-1帶來的一個比較成功的理念,就是以xml配置代替硬編碼配置信息。以往決定Jsp往哪個servlet提交,是要寫進Jsp代碼中的,也就是說一旦這個提交路徑要改,我們必須改寫代碼再重新編譯。而Struts提出來的思路是,編碼的只是一個邏輯名字,它對應哪個class文件寫進了xml配置文件中,這個配置文件記錄著所有的映射關系,一旦需要改變路徑,改變xml文件比改變代碼要容易得多。這個理念可以說相當成功,以致于后來的框架都延續著這個思路,xml所起的作用也越來越大。
大致上來說Struts當初給我們帶來的新鮮感就這么多了,其他的所有特性都是基于方便的控制轉發和可擴展的xml配置的基礎之上來完成它們的功能的。
下面將分別介紹Action和FormBean, 這兩個是Struts中最核心的兩個組件。
2. 后端控制器Action
Action就是我們說的后端控制器,它必須繼承自一個Action父類,Struts設計了很多種Action,例如DispatchAction、
DynaValidationAction。它們都有一個處理業務邏輯的方法execute(),傳入的request, response,
formBean和actionMapping四個對象,返回actionForward對象。到達Action之前先會經過一個
RequestProcessor來初始化配置文件的映射關系,這里需要大家注意幾點:
1)
為了確保線程安全,在一個應用的生命周期中,Struts框架只會為每個Action類創建一個Action實例,所有的客戶請求共享同一個Action
實例,并且所有線程可以同時執行它的execute()方法。所以當你繼承父類Action,并添加了private成員變量的時候,請記住這個變量可以被多個線程訪問,它的同步必須由程序員負責。(所有我們不推薦這樣做)。在使用Action的時候,保證線程安全的重要原則是在Action類中僅僅使用局部變量,謹慎的使用實例變量。局部變量是對每個線程來說私有的,execute方法結束就被銷毀,而實例變量相當于被所有線程共享。
2) 當ActionServlet實例接收到Http請求后,在doGet()或者doPost()方法中都會調用process()方法來處理請求。
RequestProcessor類包含一個HashMap,作為存放所有Action實例的緩存,每個Action實例在緩存中存放的屬性key為
Action類名。在RequestProcessor類的processActionCreate()方法中,首先檢查在HashMap中是否存在
Action實例。創建Action實例的代碼位于同步代碼塊中,以保證只有一個線程創建Action實例。一旦線程創建了Action實例并把它存放到
HashMap中,以后所有的線程會直接使用這個緩存中的實例。
3) <action> 元素的 <roles>
屬性指定訪問這個Action用戶必須具備的安全角色,多個角色之間逗號隔開。RequestProcessor類在預處理請求時會調用自身的
processRoles()方法,檢查配置文件中是否為Action配置了安全角色,如果有,就調用HttpServletRequest的
isUserInRole()方法來判斷用戶是否具備了必要的安全性角色,如果不具備,就直接向客戶端返回錯誤。(返回的視圖通過 <input>
屬性來指定)
3. 數據傳輸對象FormBean
Struts并沒有把模型層的業務對象直接傳遞到視圖層,而是采用DTO(Data
Transfer Object)來傳輸數據,這樣可以減少傳輸數據的冗余,提高傳輸效率;還有助于實現各層之間的獨立,使每個層分工明確。Struts的DTO就是
ActionForm,即formBean。由于模型層應該和Web應用層保持獨立。由于ActionForm類中使用了Servlet API,
因此不提倡把ActionForm傳遞給模型層, 而應該在控制層把ActionForm Bean的數據重新組裝到自定義的DTO中,
再把它傳遞給模型層。它只有兩個scope,分別是session和request。(默認是session)一個ActionForm標準的生命周期是:
1)
控制器收到請求 ->
2) 從request或session中取出ActionForm實例,如不存在就創建一個 ->
3)
調用ActionForm的reset()方法 ->
4) 把實例放入session或者request中 ->
5)
將用戶輸入表達數據組裝到ActionForm中 ->
6) 如眼張方法配置了就調用validate()方法 ->
7)
如驗證錯誤就轉發給 <input> 屬性指定的地方,否則調用execute()方法
validate()方法調用必須滿足兩個條件:
1) ActionForm 配置了Action映射而且name屬性匹配
2) <aciton> 元素的validate屬性為true
如果ActionForm在request范圍內,那么對于每個新的請求都會創建新的ActionForm實例,屬性被初始化為默認值,那么reset
()方法就顯得沒有必要;但如果ActionForm在session范圍內,同一個ActionForm實例會被多個請求共享,reset()方法在這種情況下極為有用。
4. 驗證框架和國際化
Struts有許多自己的特性,但是基本上大家還是不太常用,說白了它們也是基于JDK中間的很多Java基礎包來完成工作。例如國際化、驗證框架、插件自擴展功能、與其他框架的集成、因為各大框架基本都有提供這樣的特性,Struts也并不是做得最好的一個,這里也不想多說。Struts的驗證框架,是通過一個validator.xml的配置文件讀入驗證規則,然后在validation-rules.xml里面找到驗證實現通過自動為Jsp插入
Javascript來實現,可以說做得相當簡陋。彈出來的JavaScript框不但難看還很多冗余信息,筆者寧愿用formBean驗證或者
Action的saveErrors(),驗證邏輯雖然要自己寫,但頁面隱藏/浮現的警告提示更加人性化和美觀一些。
至于Struts的國際化,其實無論哪個框架的國際化,java.util.Locale類是最重要的Java
I18N類。在Java語言中,幾乎所有的對國際化和本地化的支持都依賴于這個類。如果Java類庫中的某個類在運行的時候需要根據Locale對象來調整其功能,那么就稱這個類是本地敏感的(Locale-Sensitive),
例如java.text.DateFormat類就是,依賴于特定Locale。
創建Locale對象的時候,需要明確的指定其語言和國家的代碼,語言代碼遵從的是ISO-639規范,國家代碼遵從ISO-3166規范,可以從
http://www.unicode.org/unicode/onlinedat/languages.html
http://www.unicode.org/unicode/onlinedat/countries.htm
Struts的國際化是基于properties的message/key對應來實現的,筆者曾寫過一個程序,所有Jsp頁面上沒有任何Text文本串,全部都用的是
<bean:message>
去Properties文件里面讀,這個時候其實只要指定不同的語言區域讀不同的Properties文件就實現了國際化。需要注意的是不同語言的字符寫進Properties文件的時候需要轉化成Unicode碼,JDK已經帶有轉換的功能。JDK的bin目錄中有native2ascii這個命令,可以完成對*.txt和*.properties的Unicode碼轉換。
這次準備繼續上次的話題先講講Struts-2,手下簡短回顧一段歷史:隨著時間的推移,Web應用框架經常變化的需求,產生了幾個下一代
Struts的解決方案。其中的Struts Ti 繼續堅持 MVC模式的基礎上改進,繼續Struts的成功經驗。
WebWork項目是在2002年3月發布的,它對Struts式框架進行了革命性改進,引進了不少新的思想,概念和功能,但和原Struts代碼并不兼
容。WebWork是一個成熟的框架,經過了好幾次重大的改進與發布。在2005年12月,WebWork與Struts Ti決定合拼, 再此同時, Struts
Ti 改名為 Struts Action Framework 2.0,成為Struts真正的下一代。
看看Struts-2的處理流程:
1) Browser產生一個請求并提交框架來處理:根據配置決定使用哪些攔截器、action類和結果等。
2)
請求經過一系列攔截器:根據請求的級別不同攔截器做不同的處理。這和Struts-1的RequestProcessor類很相似。
3) 調用Action:
產生一個新的action實例,調用業務邏輯方法。
4) 調用產生結果:匹配result class并調用產生實例。
5)
請求再次經過一系列攔截器返回:過程也可配置減少攔截器數量
6) 請求返回用戶:從control返回servlet,生成Html。
這里很明顯的一點是不存在FormBean的作用域封裝,直接可以從Action中取得數據。 這里有一個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
配置定義了名稱(供關聯)和filter的類。filter mapping讓URI匹配成功的的請求調用該filter。默認情況下,擴展名為 ".action
"。這個是在default.properties文件里的 "struts.action.extension "屬性定義的。
default.properties是屬性定義文件,通過在項目classpath路徑中包含一個名為“struts.properties”的文件來設置不同的屬性值。而Struts-2的默認配置文件名為struts.xml。由于1和2的action擴展名分別為.do和.action,所以很方便能共存。我們再來看一個Struts-2的action代碼:
public class MyAction {
public String execute() throws Exception {
//do the work
return "success ";
}
}
很明顯的區別是不用再繼承任何類和接口,返回的只是一個String,無參數。實際上在Struts-2中任何返回String的無參數方法都可以通過配置來調用action。所有的參數從哪里來獲得呢?答案就是Inversion
of
Control技術(控制反轉)。筆者盡量以最通俗的方式來解釋,我們先試圖讓這個Action獲得reuqest對象,這樣可以提取頁面提交的任何參數。那么我們把request設為一個成員變量,然后需要一個對它的set方法。由于大部分的action都需要這么做,我們把這個set方法作為接口來實現。
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;
}
}
那么誰來調用這個set方法呢?也就是說誰來控制這個action的行為,以往我們都是自己在適當的地方寫上一句
action.setServletRequest(…),也就是控制權在程序員這邊。然而控制反轉的思想是在哪里調用交給正在運行的容器來決定,只要利用Java反射機制來獲得Method對象然后調用它的invoke方法傳入參數就能做到,這樣控制權就從程序員這邊轉移到了容器那邊。程序員可以減輕很多繁瑣的工作更多的關注業務邏輯。Request可以這樣注入到action中,其他任何對象也都可以。為了保證action的成員變量線程安全,
Struts-2的action不是單例的,每一個新的請求都會產生一個新的action實例。
那么有人會問,到底誰來做這個對象的注入工作呢?答案就是攔截器。攔截器又是什么東西?筆者再來盡量通俗的解釋攔截器的概念。大家要理解攔截器的話,首先一定要理解GOF23種設計模式中的Proxy模式。
A對象要調用f(),它希望代理給B來做,那么B就要獲得A對象的引用,然后在B的f()中通過A對象引用調用A對象的f()方法,最終達到A的f()被調用的目的。有沒有人會覺得這樣很麻煩,為什么明明只要A.f()就可以完成的一定要封裝到B的f()方法中去?有哪些好處呢?
1) 這里我們只有一個A,當我們有很多個A的時候,只需要監視B一個對象的f()方法就可以從全局上控制所有被調用的f()方法。
2)
另外,既然代理人B能獲得A對象的引用,那么B可以決定在真正調A對象的f()方法之前可以做哪些前置工作,調完返回前可有做哪些后置工作。
講到這里,大家看出來一點攔截器的概念了么?它攔截下一調f()方法的請求,然后統一的做處理(處理每個的方式還可以不同,解析A對象就可以辨別),處理完畢再放行。這樣像不像對流動的河水橫切了一刀,對所有想通過的水分子進行搜身,然后再放行?這也就是AOP(Aspect
of Programming面向切面編程)的思想。
Anyway,Struts-2只是利用了AOP和IoC技術來減輕action和框架的耦合關系,力圖到最大程度重用action的目的。在這樣的技術促動下,Struts-2的action成了一個簡單被框架使用的POJO(Plain
Old Java
Object)罷了。實事上AOP和IoC的思想已經遍布新出來的每一個框架上,他們并不是多么新的技術,利用的也都是JDK早已可以最到的事情,它們代表的是更加面向接口編程,提高重用,增加擴展性的一種思想。Struts-2只是部分的使用這兩種思想來設計完成的,另外一個最近很火的框架
Spring,更大程度上代表了這兩種設計思想,筆者將于下一篇來進一步探討Spring的結構。
Spring
引用《Spring2.0技術手冊》上的一段話:
Spring的核心是個輕量級容器,它是實現IoC容器和非侵入性的框架,并提供AOP概念的實現方式;提供對持久層、事務的支持;提供MVC
Web框架的實現,并對于一些常用的企業服務API提供一致的模型封裝,是一個全方位的應用程序框架,除此之外,對于現存的各種框架,Spring也提供了與它們相整合的方案。
接下來筆者先談談自己的一些理解吧,Spring框架的發起者之前一本很著名的書名字大概是《J2ee Development without
EJB》,他提倡用輕量級的組件代替重量級的EJB。筆者還沒有看完那本著作,只閱讀了部分章節。其中有一點分析覺得是很有道理的:
EJB里在服務器端有Web Container和EJB Container,從前的觀點是各層之間應該在物理上隔離,Web
Container處理視圖功能、在EJB Container中處理業務邏輯功能、然后也是EBJ
Container控制數據庫持久化。這樣的層次是很清晰,但是一個很嚴重的問題是Web Container和EJB
Container畢竟是兩個不同的容器,它們之間要通信就得用的是RMI機制和JNDI服務,同樣都在服務端,卻物理上隔離,而且每次業務請求都要遠程調用,有沒有必要呢?看來并非隔離都是好的。
再看看輕量級和重量級的區別,筆者看過很多種說法,覺得最有道理的是輕量級代表是POJO + IoC,重量級的代表是Container +
Factory。(EJB2.0是典型的重量級組件的技術)我們盡量使用輕量級的Pojo很好理解,意義就在于兼容性和可適應性,移植不需要改變原來的代碼。而Ioc與Factory比起來,Ioc的優點是更大的靈活性,通過配置可以控制很多注入的細節,而Factory模式,行為是相對比較封閉固定的,生產一個對象就必須接受它全部的特點,不管是否需要。其實輕量級和重量級都是相對的概念,使用資源更少、運行負載更小的自然就算輕量。
話題扯遠了,因為Spring框架帶來了太多可以探討的地方。比如它的非侵入性:指的是它提供的框架實現可以讓程序員編程卻感覺不到框架的存在,這樣所寫的代碼并沒有和框架綁定在一起,可以隨時抽離出來,這也是Spring設計的目標。Spring是唯一可以做到真正的針對接口編程,處處都是接口,不依賴綁定任何實現類。同時,Spring還設計了自己的事務管理、對象管理和Model2
的MVC框架,還封裝了其他J2ee的服務在里面,在實現上基本都在使用依賴注入和AOP的思想。由此我們大概可以看到Spring是一個什么概念上的框架,代表了很多優秀思想,值得深入學習。筆者強調,學習并不是框架,而是框架代表的思想,就像我們當初學Struts一樣……
1.Spring MVC
關于IoC和AOP筆者在上篇已經稍微解釋過了,這里先通過Spring的MVC框架來給大家探討一下Spring的特點吧。(畢竟大部分人已經很熟悉Struts了,對比一下吧)
眾所周知MVC的核心是控制器。類似Struts中的ActionServlet,Spring里面前端控制器叫做DispatcherServlet。里面充當Action的組件叫做Controller,返回的視圖層對象叫做ModelAndView,提交和返回都可能要經過過濾的組件叫做
Interceptor。
讓我們看看一個從請求到返回的流程吧:
(1)
前臺Jsp或Html通過點擊submit,將數據裝入了request域
(2)
請求被Interceptor攔截下來,執行preHandler()方法出前置判斷
(3) 請求到達DispathcerServlet
(4)
DispathcerServlet通過Handler Mapping來決定每個reuqest應該轉發給哪個后端控制器Controlle
ORM
其實J2ee的規范指南里面就已經包括了一些對象持久化技術,例如JDO(Java Data
Object)就是Java對象持久化的新規范,一個用于存取某種數據倉庫中的對象的標準化API,提供了透明的對象存儲,對開發人員來說,存儲數據對象完全不需要額外的代碼(如JDBC
API的使用)。這些繁瑣的工作已經轉移到JDO產品提供商身上,使開發人員解脫出來,從而集中時間和精力在業務邏輯上。另外,JDO很靈活,因為它可以在任何數據底層上運行。JDBC只是面向關系數據庫(RDBMS)JDO更通用,提供到任何數據底層的存儲功能,比如關系數據庫、文件、XML以及對象數據庫(ODBMS)等等,使得應用可移植性更強。我們如果要理解對象持久化技術,首先要問自己一個問題:為什么傳統的JDBC來持久化不再能滿足大家的需求了呢?
筆者認為最好是能用JDBC真正編寫過程序了才能真正體會ORM的好處,同樣的道理,真正拿Servlet/Jsp做過項目了才能體會到Struts、
Spring等框架的方便之處。很幸運的是筆者這兩者都曾經經歷過,用混亂的內嵌Java代碼的Jsp加Servlet轉發寫過完整的Web項目,也用
JDBC搭建過一個完整C/S項目的后臺。所以現在接觸到新框架才更能體會它們思想和實現的優越之處,回顧從前的代碼,真是丑陋不堪啊。^_^
回到正題,我們來研究一下為什么要從JDBC發展到ORM。簡單來說,傳統的JDBC要花大量的重復代碼在初始化數據庫連接上,每次增刪改查都要獲得
Connection對象,初始化Statement,執行得到ResultSet再封裝成自己的List或者Object,這樣造成了在每個數據訪問方法中都含有大量冗余重復的代碼,考慮到安全性的話,還要加上大量的事務控制和log記錄。雖然我們學習了設計模式之后,可以自己定義Factory來幫助減少一部分重復的代碼,但是仍然無法避免冗余的問題。其次,隨著OO思想深入人心,連典型的過程化語言Perl等都冠冕堂皇的加上了OO的外殼,何況是
Java中繁雜的數據庫訪問持久化技術呢?強調面向對象編程的結果就是找到一個橋梁,使得關系型數據庫存儲的數據能準確的映射到Java的對象上,然后針對Java對象來設計對象和方法,如果我們把數據庫的Table當作Class,Record當作Instance的話,就可以完全用面向對象的思想來編寫數據層的代碼。于是乎,Object
Relationship Mapping的概念開始普遍受到重視,盡管很早很早就已經有人提出來了。
缺點我們已經大概清楚了,那么如何改進呢?對癥下藥,首先我們要解決的是如何從Data Schema準備完美的映射到Object
Schema,另外要提供對數據庫連接對象生命周期的管理,對事務不同粒度的控制和考慮到擴展性后提供對XML、Properties等可配置化的文件的支持。到目前為止,有很多框架和技術在嘗試著這樣做。例如似乎是封裝管理得過了頭的EJB、很早就出現目前已經不在開發和升級了的Apache
OJB、首先支持Manual SQL的iBATIS,還有公認非常優秀的Hibernate等等。在分別介紹它們之前,我還想反復強調這些框架都在試圖做什么:
畢竟Java
Object和數據庫的每一條Record還是有很大的區別,就是類型上來說,DB是沒有Boolean類型的。而Java也不得不用封裝類(Integer、Double等)為了能映射上數據庫中為null的情況,畢竟Primitive類型是沒有null值的。還有一個比較明顯的問題是,數據庫有主鍵和外鍵,而Java中仍然只能通過基本類型來對應字段值而已,無法規定Unique等特征,更別提外鍵約束、事務控制和級聯操作了。另外,通過Java
Object預設某Field值去取數據庫記錄,是否在這樣的記錄也是不能保證的。真的要設計到完全映射的話,Java的Static被所有對象共享的變量怎么辦?在數據庫中如何表現出來……
我們能看到大量的問題像一座座大山橫在那些框架設計者們面前,他們并不是沒有解決辦法,而是從不同的角度去考慮,會得到很多不同的解決方案,問題是應該采取哪一種呢?甚至只有等到真正設計出來了投入生產使用了,才能印證出當初的設想是否真的能為項目開發帶來更多的益處。筆者引用一份文檔中提到一個健壯的持久化框架應該具有的特點:
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)
現在來簡短的介紹一下筆者用過的一些持久化框架和技術,之所以前面強調那么多共通的知識,是希望大家不要盲從流行框架,一定要把握它的本質和卓越的思想好在哪里。
1. Apache OJB
OJB代表Apache Object Relational
Bridge,是Apache開發的一個數據庫持久型框架。它是基于J2ee規范指南下的持久型框架技術而設計開發的,例如實現了ODMG
3.0規范的API,實現了JDO規范的API, 核心實現是Persistence Broker API。OJB使用XML文件來實現映射并動態的在Metadata
layer聽過一個Meta-Object-Protocol(MOP)來改變底層數據的行為。更高級的特點包括對象緩存機制、鎖管理機制、 Virtual
代理、事務隔離性級別等等。舉個OJB Mapping的簡單例子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最大的特點就是允許用戶自己定義SQL來組配Bean的屬性。因為它的SQL語句是直接寫入XML文件中去的,所以可以最大程度上利用到
SQL語法本身能控制的全部特性,同時也能允許你使用特定數據庫服務器的額外特性,并不局限于類似SQL92這樣的標準,它最大的缺點是不支持枚舉類型的持久化,即把枚舉類型的幾個對象屬性拼成與數據庫一個字段例如VARCHAR對應的行為。這里也舉一個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無疑是應用最廣泛最受歡迎的持久型框架,它生成的SQL語句是非常優秀。雖然一度因為不能支持手工SQL而性能受到局限,但隨著新一代
Hibernate
3.x推出,很多缺點都被改進,Hibernate也因此變得更加通用而時尚。同樣先看一個Mapping文件的例子customer.hbm.xml來有一個大概印象:
<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有很多顯著的特性,最突出的就是它有自己的查詢語言叫做HQL,在HQL中select
from的不是Table而是類名,一方面更加面向對象,另外一方面通過在hibernate.cfg.xml中配置Dialect為HQL可以使得整個后臺與數據庫脫離耦合,因為不管用那種數據庫我都是基于HQL來查詢,Hibernate框架負責幫我最終轉換成特定數據庫里的SQL語句。另外
Hibernate在Object-Caching這方面也做得相當出色,它同時管理兩個級別的緩存,當數據被第一次取出后,真正使用的時候對象被放在一級緩存管理,這個時候任何改動都會影響到數據庫;而空閑時候會把對象放在二級緩存管理,雖然這個時候與數據庫字段能對應上但未綁定在一起,改動不會影響到數據庫的記錄,主要目的是為了在重復讀取的時候更快的拿到數據而不用再次請求連接對象。其實關于這種緩存的設計建議大家研究一下Oracle的存儲機制(原理是相通的),Oracle犧牲了空間換來時間依賴于很健壯的緩存算法來保證最優的企業級數據庫訪問速率。
以上是一些Mapping的例子,真正在Java代碼中使用多半是繼承各個框架中默認的Dao實現類,然后可以通過Id來查找對象,或者通過
Example來查找,更流行的是更具Criteria查找對象。Criteria是完全封裝了SQL條件查詢語法的一個工具類,任何一個查詢條件都可以在Criteria中找到方法與之對應,這樣可以在Java代碼級別實現SQL的完全控制。另外,現在許多ORM框架的最新版本隨著JDk
5.0加入Annotation特性都開始支持用XDoclet來自動根據Annotation來生成XML配置文件了。
Author: orangelizq
email: orangelizq@163.com
posted on 2009-08-27 17:09
桔子汁 閱讀(407)
評論(0) 編輯 收藏 所屬分類:
J2EE