學會java應該瞭解這些(2)
終於又靜下來繼續寫這個主題的續篇,前六篇主要講了一些J2se方面的經驗和感受, 眼下Java應用範圍已經被J2ee佔據了相當大的一塊領域,有些人甚至聲稱Java被J2ee所取代了。不知道大家如何來理解所謂的J2ee (Java2 Enterprise Edition),也就是Java企業級應用?
筆者的觀點是,技術的發展是順應世界變化的趨勢的,從C/S過渡到B/S模式,從客戶端的角度考慮企業級應用或者說電子商務領域不在關心客戶端維護問題,這個任務已經交給了任何一臺PC都會有的瀏覽器去維護;從服務器端的角度考慮,以往C/S中的TCP/IP協議實現載體ServerSocket被Web Server Container所取代,例如大家都很熟悉的Tomcat、JBoss、WebLogic等等。總之一切的轉變都是爲了使得Java技術能更好的爲人類生產生活所服務。
有人會問,直接去學J2ee跳過J2se行否?筆者是肯定不贊成的,實際上確實有人走這條路,但筆者自身體會是正是由於J2se的基礎很牢固,纔會導致在J2ee學習的道路上順風順水,知識點上不會有什麼迷惑的地方。舉個簡單的例子吧:
筆者曾經跟大學同學討論下面這兩種寫法的區別:
ArrayList list = new ArrayList(); //筆者不說反對,但至少不贊成
List list = new ArrayList(); //筆者支持
曾經筆者跟同學爭論了幾個小時,他非說第一種寫法更科學,第二種完全沒有必要。我無法完全說服他,但筆者認爲良好的習慣和意識是任何時候都應該針對接口編程,以達到解耦合和可擴展性的目的。下面就以接口開始進入J2ee的世界吧:
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等,由模型層處理完了持久化到數據庫中)
當然,這僅僅是筆者個人的觀點,僅僅是供大家學習做一個參考,如果要實現這些層之間的完全分離,那麼一個大的工程,可以僅僅通過增加人手就來完成任務。雖然《人月神話》中已經很明確的闡述了增加人手並不能是效率增加,很大程度上是因爲彼此做的工作有順序上的依賴關係或者說難度和工作量上的巨大差距。當然理想狀態在真實世界中是不可能達到的,但我們永遠應該朝着這個方向去不斷努力。最開始所提倡的針對接口來編程,哪怕是小小的細節,寫一條List list= = new ArrayList()語句也能體現着處處皆使用接口的思想在裏面。Anyway,這只是個開篇,筆者會就自己用過的J2ee技術和框架再細化談一些經驗
####################################################################################################################
Java雜談(八)--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必經之路,也是最基礎的知識,希望大家給與足夠的重視!
######################################################################################################################
Java雜談(九)--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碼轉換。
OK,今天就說到這裏,本文中的很多內容也不是筆者的手筆,是筆者一路學習過來自己抄下來的筆記,希望對大家有幫助!Java雜談一路走來,感謝大家持續的關注,大概再有個2到3篇續篇就改完結了!筆者儘快整理完成後續的寫作吧……^_^
##############################################################################################################
Java雜談(九)--Struts2
最近業餘時間筆者一直Java Virtual Machine的研究,由於實習分配到項目組裏面,不想從前那麼閒了,好不容易纔抽出時間來繼續這個話題的帖子。我打算把J2ee的部分結束之後,再談談 JVM和JavaScript,只要筆者有最新的學習筆記總結出來,一定會拿來及時和大家分享的。衷心希望與熱愛Java的關大同仁共同進步……
這次準備繼續上次的話題先講講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的結構。
PS: 關於Struts-2筆者也沒真正怎麼用過,這裏是看了網上一些前輩的帖子之後寫下自己的學習體驗,不足之處請見諒!
##################################################################################################################
Java雜談(十)--Spring
筆者最近比較忙,一邊在實習一邊在尋找明年畢業更好的工作,不過論壇裏的朋友非常支持小弟繼續寫,今天是週末,泡上一杯咖啡,繼續與大家分享J2ee部分的學習經驗。今天的主題是目前很流行也很好的一個開源框架-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
筆者的觀點是,技術的發展是順應世界變化的趨勢的,從C/S過渡到B/S模式,從客戶端的角度考慮企業級應用或者說電子商務領域不在關心客戶端維護問題,這個任務已經交給了任何一臺PC都會有的瀏覽器去維護;從服務器端的角度考慮,以往C/S中的TCP/IP協議實現載體ServerSocket被Web Server Container所取代,例如大家都很熟悉的Tomcat、JBoss、WebLogic等等。總之一切的轉變都是爲了使得Java技術能更好的爲人類生產生活所服務。
有人會問,直接去學J2ee跳過J2se行否?筆者是肯定不贊成的,實際上確實有人走這條路,但筆者自身體會是正是由於J2se的基礎很牢固,纔會導致在J2ee學習的道路上順風順水,知識點上不會有什麼迷惑的地方。舉個簡單的例子吧:
筆者曾經跟大學同學討論下面這兩種寫法的區別:
ArrayList list = new ArrayList(); //筆者不說反對,但至少不贊成
List list = new ArrayList(); //筆者支持
曾經筆者跟同學爭論了幾個小時,他非說第一種寫法更科學,第二種完全沒有必要。我無法完全說服他,但筆者認爲良好的習慣和意識是任何時候都應該針對接口編程,以達到解耦合和可擴展性的目的。下面就以接口開始進入J2ee的世界吧:
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等,由模型層處理完了持久化到數據庫中)
當然,這僅僅是筆者個人的觀點,僅僅是供大家學習做一個參考,如果要實現這些層之間的完全分離,那麼一個大的工程,可以僅僅通過增加人手就來完成任務。雖然《人月神話》中已經很明確的闡述了增加人手並不能是效率增加,很大程度上是因爲彼此做的工作有順序上的依賴關係或者說難度和工作量上的巨大差距。當然理想狀態在真實世界中是不可能達到的,但我們永遠應該朝着這個方向去不斷努力。最開始所提倡的針對接口來編程,哪怕是小小的細節,寫一條List list= = new ArrayList()語句也能體現着處處皆使用接口的思想在裏面。Anyway,這只是個開篇,筆者會就自己用過的J2ee技術和框架再細化談一些經驗
####################################################################################################################
Java雜談(八)--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必經之路,也是最基礎的知識,希望大家給與足夠的重視!
######################################################################################################################
Java雜談(九)--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碼轉換。
OK,今天就說到這裏,本文中的很多內容也不是筆者的手筆,是筆者一路學習過來自己抄下來的筆記,希望對大家有幫助!Java雜談一路走來,感謝大家持續的關注,大概再有個2到3篇續篇就改完結了!筆者儘快整理完成後續的寫作吧……^_^
##############################################################################################################
Java雜談(九)--Struts2
最近業餘時間筆者一直Java Virtual Machine的研究,由於實習分配到項目組裏面,不想從前那麼閒了,好不容易纔抽出時間來繼續這個話題的帖子。我打算把J2ee的部分結束之後,再談談 JVM和JavaScript,只要筆者有最新的學習筆記總結出來,一定會拿來及時和大家分享的。衷心希望與熱愛Java的關大同仁共同進步……
這次準備繼續上次的話題先講講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的結構。
PS: 關於Struts-2筆者也沒真正怎麼用過,這裏是看了網上一些前輩的帖子之後寫下自己的學習體驗,不足之處請見諒!
##################################################################################################################
Java雜談(十)--Spring
筆者最近比較忙,一邊在實習一邊在尋找明年畢業更好的工作,不過論壇裏的朋友非常支持小弟繼續寫,今天是週末,泡上一杯咖啡,繼續與大家分享J2ee部分的學習經驗。今天的主題是目前很流行也很好的一個開源框架-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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.