天天看點

一個計算機專業學生幾年的程式設計經驗彙總

  想來學習 Java 也有兩個年頭了,永遠不敢說多麼精通,但也想談談自己的感受,寫給軟體學院的同仁們,幫助大家在技術的道路上少一點彎路。說得偉大一點是希望大家為軟體學院争氣,其實最主要的還是大家自身的進步提升 ??

1 .   關于動态加載機制 ??

學習 Java 比 C++ 更容易了解 OOP 的思想,畢竟 C++ 還混合了不少面向過程的成分。很多人都能背出來 Java 語言的特點,所謂的動态加載機制等等。當然概念往往是先記住而後消化的,可有多少人真正去體會過動态加載的機制,試圖去尋找過其中的細節呢 ?    提供大家一個方法:

在指令行視窗運作 Java 程式的時候,加上這個很有用的參數:

java   ?verbose   *.class

這樣會清晰的列印出被加載的類檔案,大部分是 jdk 自身運作需要的,最後幾行會明顯的看到自己用到的那幾個類檔案被加載進來的順序。即使你聲明了一個類對象,不執行個體化也不會加載,說明隻有真正用到那個類的執行個體即對象的時候,才會執行加載。這樣是不是大家稍微能明白一點動态加載了呢? ^_^

2 .   關于尋找 class 檔案原理 ??

建議大家在入門的時候在指令行視窗編譯和運作,不要借助 JCreator 或者 Eclipse 等 IDE 去幫助做那些事情。嘗試自己這樣做:

javac     -classpath   yourpath   *.java

java     -classpath   yourpath   *.class

也許很多人都能看懂,設定 classpath 的目的就是告訴編譯器去哪裡尋找你的 class 檔案 .    不過至少筆者今日才弄懂 JVM 去查詢類的原理,編譯器加載類要依靠 classloader ,     而 classloader 有 3 個級别,從高到低分别是 BootClassLoader( 名字可能不準确 )   ,   ExtClassLoader,   AppClassLoader.

這 3 個加載器分别對應着編譯器去尋找類檔案的優先級别和不同的路徑: BootClassLoader 對應 jre/classes 路徑,是編譯器最優先尋找 class 的地方

ExtClassLoader 對應 jre/lib/ext 路徑,是編譯器次優先尋找 class 的地方

AppClassLoader 對應目前路徑,是以也是編譯器預設找 class 的地方

其實大家可以自己寫個程式簡單的測試,對任何 class ,例如 A,  

調用 new   A().getClass().getClassLoader().toString()    列印出來就可以看到,把 class 檔案放在不同的路徑下再次執行,就會看到差別。特别注意的是如果列印出來是 null 就表示到了最進階  BootClassLoader,    因為它是 C++ 編寫的,不存在 Java 對應的類加載器的名字。

尋找的順序是一種向上迂回的思想,即如果本級别找不到,就隻能去本級别之上的找,不會向下尋找。不過似乎從 Jdk1.4 到 Jdk1.6 這一特點又有改變,沒有找到詳細資料。是以就不舉例子了。告訴大家設計這種體系的是 Sun 公司曾經的技術核心宮力先生,一個純種華人哦! ^_^

這樣希望大家不至于迷惑為什麼總報錯找不到類檔案,不管是自己寫的還是導入的第三方的 jar 檔案( J2ee 中經常需要導入的)。

3 .   關于 jdk 和 jre??

大家肯定在安裝 JDK 的時候會有選擇是否安裝單獨的 jre ,一般都會一起安裝,我也建議大家這樣做。因為這樣更能幫助大家弄清楚它們的差別:

Jre    是 java   runtime   environment,    是 java 程式的運作環境。既然是運作,當然要包含 jvm ,也就是大家熟悉的虛拟機啦,     還有所有 java 類庫的 class 檔案,都在 lib 目錄下打包成了 jar 。大家可以自己驗證。至于在 windows 上的虛拟機是哪個檔案呢?     學過 MFC 的都知道什麼是 dll 檔案吧,那麼大家看看 jre/bin/client 裡面是不是有一個 jvm.dll 呢?那就是虛拟機。

Jdk    是 java   development   kit ,是 java 的開發工具包,裡面包含了各種類庫和工具。當然也包括了另外一個 Jre.      那麼為什麼要包括另外一個 Jre 呢?而且 jdk/jre/bin 同時有 client 和 server 兩個檔案夾下都包含一個 jvm.dll 。     說明是有兩個虛拟機的。這一點不知道大家是否注意到了呢?

相信大家都知道 jdk 的 bin 下有各種 java 程式需要用到的指令,與 jre 的 bin 目錄最明顯的差別就是 jdk 下才有 javac ,這一點很好了解,因為  jre 隻是一個運作環境而已。與開發無關,正因為如此,具備開發功能的 jdk 自己的 jre 下才會同時有 client 性質的 jvm 和 server 性質的  jvm ,     而僅僅作為運作環境的 jre 下隻需要 client 性質的 jvm.dll 就夠了。

記得在環境變量 path 中設定 jdk/bin 路徑麽?這應該是大家學習 Java 的第一步吧,     老師會告訴大家不設定的話 javac 和 java 是用不了的。确實 jdk/bin 目錄下包含了所有的指令。可是有沒有人想過我們用的 java 指令并不是  jdk/bin 目錄下的而是 jre/bin 目錄下的呢?不信可以做一個實驗,大家可以把 jdk/bin 目錄下的 java.exe 剪切到别的地方再運作  java 程式,發現了什麼?一切 OK !

那麼有人會問了?我明明沒有設定 jre/bin 目錄到環境變量中啊?

試想一下如果 java 為了提供給大多數人使用,他們是不需要 jdk 做開發的,隻需要 jre 能讓 java 程式跑起來就可以了,那麼每個客戶還需要手動去設定環境變量多麻煩啊?是以安裝 jre 的時候安裝程式自動幫你把 jre 的 java.exe 添加到了系統變量中,驗證的方法很簡單,大家看到了系統環境變量的  path 最前面有 “%SystemRoot%/system32;%SystemRoot%;” 這樣的配置,那麼再去 Windows/system32 下面去看看吧,發現了什麼?有一個 java.exe 。

如果強行能夠把 jdk/bin 挪到 system32 變量前面,當然也可以迫使使用 jdk/jre 裡面的 java ,不過除非有必要,我不建議大家這麼做。使用單獨的 jre 跑 java 程式也算是客戶環境下的一種測試。

這下大家應該更清楚 jdk 和 jre 内部的一些聯系和差別了吧?

PS:    其實還有滿多感想可以總結的,一次寫多了怕大家扔磚頭砸死我,怪我太羅唆。大家應該更加踏實更加務實的去做一些研究并互相分享心得,大方向和太前沿的技術讨論是必要的但最好不要太多,畢竟自己基礎都還沒打好,什麼都講最新版本其實是進步的一大障礙!

#########################################################################################################################

Java    學習雜談(二)

鑒于上回寫的一點感想大家不嫌棄,都鼓勵小弟繼續寫下去,好不容易等到國慶黃金周,實習總算有一個休息的階段,于是這就開始寫第二篇了。希望這次寫的仍然對志同道合的朋友們有所幫助。上回講了 Java 動态加載機制、 classLoader 原理和關于 jdk 和 jre 三個問題。這次延續着講一些具體的類庫 ??

1 .     關于集合架構類

相信學過 Java 的各位對這個名詞并不陌生,對    java.util.* 這個 package 肯定也不陌生。不知道大家查詢 API 的時候怎麼去審視或者分析其中的一個 package ,每個包最重要的兩個部分就是 interfaces 和 classes ,接口代表了它能做什麼,實作類則代表了它如何去做。關注實作類之前,我們應該先了解清楚它的來源接口,不管在 j2se 還是 j2ee 中,都應該是這樣。那麼我們先看這三個接口: List 、 Set 、 Map 。

也許有些人不太熟悉這三個名字,但相信大部分人都熟悉 ArrayList , LinkedList , TreeSet , HashSet , HashMap ,      Hashtable 等實作類的名字。它們的差別也是滿容易了解的, List 放可以重複的對象集合, Set 放不可重複的對象組合,而 Map 則放    <Key,Value   >  這樣的名值對,    Key 不可重複, Value 可以。這裡有幾個容易混淆的問題:

到底 Vector 和 ArrayList , Hashtable 和 HashMap 有什麼差別?

很多面試官喜歡問這個問題,其實更專業一點應該這樣問:新集合架構和舊集合架構有哪些差別?新集合架構大家可以在這些包中找 since   jdk1.2 的,之前的如 vector 和 Hashtable 都是舊的集合架構包括的類。那麼差別是?

a.    新集合架構的命名更加科學合理。例如 List 下的 ArrayList 和 LinkedList

b.    新集合架構下全部都是非線程安全的。建議去 jdk 裡面包含的源代碼裡面自己去親自看看 vector 和 ArrayList 的差別吧。當然如果是 jdk5.0 之後的會比較難看一點,因為又加入了泛型的文法,類似 c++ 的 template 文法。

那麼大家是否想過為什麼要從舊集合架構預設全部加鎖防止多線程通路更新到新集合架構全部取消鎖,預設方式支援多線程? ( 當然需要的時候可以使用 collections 的靜态方法加鎖達到線程安全 )

筆者的觀點是任何技術的發展都未必是遵循它們的初衷的,很多重大改變是受到客觀環境的影響的。大家知道 Java 的初衷是為什麼而開發的麽?是為嵌入式程式開發的。記得上一篇講到 classLoader 機制麽?那正是為了節約嵌入式開發環境下記憶體而設計的。而走到今天, Java 成了人們心中為網際網路誕生的語言。網際網路意味着什麼?多線程是必然的趨勢。客觀環境在變, Java 技術也随着飛速發展,導緻越來越脫離它的初衷。據說 Sun 公司其實主打的是 J2se ,結果又是由于客觀環境影響, J2se 幾乎遺忘,留在大家談論焦點的一直是 j2ee 。

技術的細節這裡就不多說了,隻有用了才能真正了解。解釋這些正是為了幫助大家了解正在學的和将要學的任何技術。之後講 j2ee 的時候還會再讨論。

多扯句題外話:幾十年前的 IT 巨人是 IBM , Mainframe 市場無人可比。微軟如何打敗 IBM ?正是由于硬體飛速發展,對個人 PC 的需求這個客觀環境,讓微軟通過 OS 稱為了第二個巨人。下一個打敗微軟的呢? Google 。如何做到的?如果微軟并不和 IBM 争大型機, Google 借着網際網路飛速發展這個客觀環境作為決定性因素,避開跟微軟争 OS ,而是走搜尋引擎這條路,稱為第 3 個巨人。那麼第 4 個巨人是誰呢?很多專家預言将在亞洲或者中國出現,    Whatever ,客觀環境變化趨勢才是決定大方向的關鍵。當然筆者也希望會出現在中國, ^_^~~

2 .     關于 Java 設計模式

身邊的很多在看 GOF 的 23 種設計模式,似乎學習它無論在學校還是在職場,都成了一種流行風氣。我不想列舉解釋這 23 種 Design   Pattern ,     我寫這些的初衷一直都是談自己的經曆和看法,希望能幫助大家了解。

首先我覺得設計模式隻是對一類問題的一種通用解決辦法,隻要是面向對象的程式設計預言都可以用得上這 23 種。了解它們最好的方法就是親自去寫每一種,哪怕是一個簡單的應用就足夠了。如果代碼實作也記不住的話,記憶它們對應的 UML 圖會是一個比較好的辦法,當然前提是必須了解 UML 。

同時最好能利用 Java 自身的類庫幫助記憶,例如比較常用的觀察者模式,在 java.util.* 有現成的 Observer 接口和 Observable 這個實作類,看看源代碼相信就足夠了解觀察者模式了。再比如裝飾器模式,大家隻要寫幾個關于 java.io.* 的程式就可以完全了解什麼是裝飾器模式了。有很多人覺得剛入門的時候不該接觸設計模式,比如圖靈設計叢書系列很出名的那本《 Java 設計模式》,作者 :   Steven   John   Metsker ,大部分例子老實說令現在的我也很迷惑。但我仍然不同意入門跟學習設計模式有任何沖突,隻是我們需要知道每種模式的概念的和典型的應用,這樣我們在第一次編寫    FileOutputStream 、 BufferedReader 、 PrintWriter 的時候就能感覺到原來設計模式離我們如此之近,而且并不是多麼神秘的東西。

另外,在學習某些模式的同時,反而更能幫助我們了解 java 類庫的某些特點。例如當你編寫原型 (Prototype) 模式的時候,你必須了解的是    java.lang.Cloneable 這個接口和所有類的基類 Object 的 clone() 這個方法。即深 copy 和淺 copy 的差別:

Object.clone() 預設實作的是淺 copy ,也就是複制一份對象拷貝,但如果對象包含其他對象的引用,不會複制引用,是以原對象和拷貝共用那個引用的對象。

深 copy 當然就是包括對象的引用都一起複制啦。這樣原對象和拷貝對象,都分别擁有一份引用對象。如果要實作深 copy 就必須首先實作    java.lang.Cloneable 接口,然後重寫 clone() 方法。因為在 Object 中的 clone() 方法是 protected 簽名的,而    Cloneable 接口的作用就是把 protected 放大到 public ,這樣 clone() 才能被重寫。

那麼又有個問題了?如果引用的對象又引用了其他對象呢?這樣一直判斷并複制下去,是不是顯得很麻煩?曾經有位前輩告訴我的方法是重寫 clone 方法的時候直接把原對象序列化到磁盤上再反序列化回來,這樣不用判斷就可以得到一個深 copy 的結果。如果大家不了解序列化的作法建議看一看    ObjectOutputStream 和 ObjectInputStream

歸根結底,模式隻是思想上的東西,把它當成前人總結的經驗其實一點都不為過。鼓勵大家動手自己去寫,例如代理模式,可以簡單的寫一個 Child 類,    Adult 類。 Child 要買任何東西由 Adult 來代理實作。簡單來說就是 Adult 裡的 buy() 内部實際調用的是 Child 的 buy() ,可是暴露在 main 函數的卻是 Adult.buy() 。這樣一個簡單的程式就足夠了解代理模式的基本含義了。   

################################################################################################################

Java    雜談(三)

         這已經筆者寫的第三篇 Java 雜記了,慶幸前兩篇一直得到論壇朋友們的支援鼓勵,還望大家繼續指正不足之處。筆者也一直渴望通過這樣方式清醒的自審,來尋找自己技術上的不足之處,希望和共同愛好 Java 的同仁們一起提高。

         前兩次分别講述了關于 jvm 、 jdk 、 jre 、 collection 、 classLoader 和一些 Design   Pattern 的自我了解。這次仍然不準備開始過渡到 j2ee 中,因為覺得還有一些瑣碎的 j2se 的問題沒有總結完畢。

          1 .     關于 Object 類了解

         大家都知道 Object 是所有 Java 類的基類,     意味着所有的 Java 類都會繼承了 Object 的 11 個方法。建議大家去看看 Object 的    11 個成員函數的源代碼,就會知道預設的實作方式。比如 equals 方法,預設實作就是用 "==" 來比較,即直接比較記憶體位址,傳回 true    或者    false 。而 toString() 方法,傳回的串組成方式是 ??

        "getClass().getName()   +   "@"   +   Integer.toHexString(hashCode())"

         其實不用我過多的解釋,大家都能看懂這個串的組成。接下來再看看 hashCode() :

        public   native   int   hashCode() ;

         由于是 native 方法,跟 OS 的處理方式相關,源代碼裡僅僅有一個聲明罷了。我們有興趣的話完全可以去深究它的 hashCode 到底是由 OS 怎麼樣産生的呢?但筆者建議最重要的還是先記住使用它的幾條原則吧!首先如果 equals() 方法相同的對象具有相通的 hashCode ,但 equals   () 對象不相通的時候并不保證 hashCode() 方法傳回不同的整數。而且下一次運作同一個程式,同一個對象未必還是當初的那個 hashCode()    哦。

         其餘的方法呢? nofigy() 、 notifyAll() 、 clone() 、 wait() 都是 native 方法的,說明依賴于作業系統的實作。最後一個有趣的方法是 finalize() ,類似 C++ 的析構函數,簽名是 protected ,證明隻有繼承擴充了才能使用,方法體是空的,默示什麼也不做。它的作用據筆者的了解僅僅是通知 JVM 此對象不再使用,随時可以被銷毀,而實際的銷毀權還是在于虛拟機手上。那麼它真的什麼也不做麽?未必,實際上如果是線程對象它會導緻在一定範圍内該線程的優先級别提高,導緻更快的被銷毀來節約記憶體提高性能。其實從常理來說,我們也可以大概這樣猜測出 jvm 做法的目的。

        2 .     關于重載 hashCode() 與 Collection 架構的關系

筆者曾經聽一位搞 Java 教育訓練多年的前輩說在他看來 hashCode 方法沒有任何意義,僅僅是為了配合證明具有同樣的 hashCode 會導緻 equals    方法相等而存在的。連有的前輩都犯這樣的錯誤,其實說明它還是滿容易被忽略的。那麼 hashCode() 方法到底做什麼用?

         學過資料結構的課程大家都會知道有一種結構叫 hash   table ,目的是通過給每個對象配置設定一個唯一的索引來提高查詢的效率。那麼 Java 也不會肆意扭曲改變這個概念,是以 hashCode 唯一的作用就是為支援資料結構中的哈希表結構而存在的,換句話說,也就是隻有用到集合架構的    Hashtable 、 HashMap 、 HashSet 的時候,才需要重載 hashCode() 方法,

這樣才能使得我們能人為的去控制在哈希結構中索引是否相等。筆者舉一個例子:

         曾經為了寫一個求解類程式,需要随機列出 1,2,3,4 組成的不同排列組合,是以筆者寫了一個數組類用 int[] 來存組合結果,然後把随機産生的組合加入一個 HashSet 中,就是想利用 HashSet 不包括重複元素的特點。可是 HashSet 怎麼判斷是不是重複的元素呢?當然是通過    hashCode() 傳回的結果是否相等來判斷啦,可做一下這個實驗:

        int[]   A   =   {1,2,3,4};

        int[]   B   =   {1,2,3,4};

        System.out.println(A.hashCode());

        System.out.println(B.hashCode());

         這明明是同一種組合,卻是不同的 hashCode ,加入 Set 的時候會被當成不同的對象。這個時候我們就需要自己來重寫 hashCode() 方法了,如何寫呢?其實也是基于原始的 hashCode() ,畢竟那是作業系統的實作,     找到相通對象唯一的辨別,實作方式很多,筆者的實作方式是:

         首先重寫了 toString() 方法 :

        return     A[0]“+”   A[1]“+”   A[2]“+”   A[3];   // 顯示上比較直覺

         然後利用 toString() 來計算 hashCode() :

        return     this.toString().hashCode() ;

         這樣上述 A 和 B 傳回的就都是 ”1234” ,在測試 toString().hashCode() ,由于 String 在記憶體中的副本是一樣的, ”1234”.hashCode() 傳回的一定是相同的結果。

         說到這,相信大家能了解得比我更好,今後千萬不要再誤解 hashCode() 方法的作用。

        3 .     關于 Class 類的成員函數與 Java 反射機制

         很早剛接觸 Java 就聽很多老師說過 Java 的動态運作時機制、反射機制等。确實它們都是 Java 的顯著特點,運作時加載筆者在第一篇介紹過了,現在想講講反射機制。在 Java 中,主要是通過 java.lang 包中的 Class 類和 Method 類來實作記憶體反射機制的。

         熟悉 C++ 的人一定知道下面這樣在 C++ 中是做不到的:     運作時以字元串參數傳遞一個類名,就可以得到這個類的所有資訊,包括它所有的方法,和方法的詳細資訊。還可以執行個體化一個對象,并通過查到的方法名來調用該對象的任何方法。這是因為 Java 的類在記憶體中除了 C++ 中也有的靜态動态資料區之外,還包括一份對類自身的描述,也正是通過這描述中的資訊,才能幫助我們才運作時讀取裡面的内容,得到需要加載目标類的所有資訊,進而實作反射機制。大家有沒有想過當我們需要得到一個 JavaBean 的執行個體的時候,怎麼知道它有哪些屬性呢?再明顯簡單不過的例子就是自己寫一個 JavaBean 的解析器:

        a.    通過 Class.forName(“Bean 的類名 ”) 得到 Class 對象,例如叫 ABeanClass

        b.    通過 ABeanClass 的 getMethods() 方法,得到 Method[] 對象

        c.    按照規範所有 get 方法名後的單詞就代表着該 Bean 的一個屬性

        d.    當已經知道一個方法名,可以調用 newInstance() 得到一個執行個體,然後通過 invoke() 方法将方法的名字和方法需要用的參數傳遞進去,就可以動态調用此方法。

         當然還有更複雜的應用,這裡就不贅述,大家可以參考 Class 類和 Method 類的方法。

        4 .     坦言 Synchronize 的本質

        Synchronize 大家都知道是同步、加鎖的意思,其實它的本質遠沒有大家想得那麼複雜。聲明 Synchronize 的方法被調用的時候,鎖其實是加載對象上,當然如果是靜态類則是加在類上的鎖,調用結束鎖被解除。它的實作原理很簡單,僅僅是不讓第二把鎖再次被加在同一個對象或類上,僅此而已。一個簡單的例子足以說明問題:

        class   A{

synchronized   void   f(){}

void   g(){}

        }

         當 A 的一個對象 a 被第一個線程調用其 f() 方法的時候,第二個線程不能調用 a 的 synchronized 方法例如 f() ,因為那是在試圖在對象上加第二把鎖。但調用 g() 卻是可以的,因為并沒有在同一對象上加兩把鎖的行為産生。

         這樣大家能了解了麽?明白它的原理能更好的幫助大家設計同步機制,不要濫用加鎖。

        PS :下篇筆者計劃開始對 J2ee 接觸到的各個方面來進行總結,談談自己的經驗和想法。希望大家還能一如既往的支援筆者寫下去,指正不足之處。

#############################################################################################################

Java 雜談(四)

         不知不覺已經寫到第四篇了,論壇裡面不斷的有朋友鼓勵我寫下去。堅持自己的作風,把一切迷惑不容易理清楚的知識講出來,講到大家都能聽懂,那麼自己就真的懂了。最近在公司實習的時候 Trainer 跟我講了很多經典事迹,對還未畢業的我來說是筆不小的财富,我自己的信念是:人在逆境中成長的速度要遠遠快過順境中,這樣來看一切都能欣然接受了。

         好了,閑話不說了,第三篇講的是反射機制集合架構之類的,這次打算講講自己對反序列化和多線程的了解。希望能對大家學習 Java 起到幫助 ??

        1 .關于序列化和反序列化

         應該大家都大概知道 Java 中序列化和反序列化的意思,序列化就是把一個 Java 對象轉換成二進制進行磁盤上傳輸或者網絡流的傳輸,反序列化的意思就是把這個接受到的二進制流重新組裝成原來的對象逆過程。它們在 Java 中分别是通過 ObjectInputStream 和    ObjectInputStream 這兩個類來實作的(以下分别用 ois 和 oos 來簡稱)。

        oos 的 writeObject() 方法用來執行序列化的過程, ois 的 readObject() 用來執行反序列化的過程,在傳輸二進制流之前,需要講這兩個高層流對象連接配接到同一個 Channel 上,這個 Channel 可以是磁盤檔案,也可以是 socket 底層流。是以無論用哪種方式,底層流對象都是以構造函數參數的形式傳遞進 oos 和 ois 這兩個高層流,連接配接完畢了才可以進行二進制資料傳輸的。例子:

         可以是檔案流通道

        file   =   new   File(“C:/data.dat”);

        oos   =   new   ObjectOutputStream(new   FileOutputStream(file));

        ois   =   new   ObjectInputStream(new   FileInputStream(file));

         或者網絡流通道

        oos   =   new   ObjectOutputStream(socket.getOutputStream());

        ois   =   new   ObjectInputStream(socket.getInputStream());  

         不知道大家是否注意到 oos 總是在 ois 之前定義,這裡不希望大家誤解這個順序是固定的麼?回答是否定的,那麼有順序要求麼?回答是肯定的。原則是什麼呢?

原則是互相對接的輸入 / 輸出流之間必須是 output 流先初始化然後再 input 流初始化,否則就會抛異常。大家肯定會問為什麼?隻要稍微看一看這兩個類的源代碼檔案就大概知道了, output 流的任務很簡單,隻要把對象轉換成二進制往通道中寫就可以了,但 input 流需要做很多準備工作來接受并最終重組這個 Object ,是以 ObjectInputStream 的構造函數中就需要用到 output 初始化發送過來的 header 資訊,這個方法叫做    readStreamHeader() ,它将會去讀兩個 Short 值用于決定用多大的緩存來存放通道發送過來的二進制流,這個緩存的 size 因 jre 的版本不同是不一樣的。是以 output 如果不先初始化, input 的構造函數首先就無法正确運作。

         對于上面兩個例子,第一個順序是嚴格的,第二個因為 oos 和 ois 連接配接的已經不是對方了,而是 socket 另外一端的流,需要嚴格按照另外一方對接的 output 流先于對接的 input 流打開才能順利運作。

         這個 writeObject 和 readObject 本身就是線程安全的,傳輸過程中是不允許被并發通路的。是以對象能一個一個接連不斷的傳過來,有很多人在運作的時候會碰到 EOFException,    然後百思不得其解,去各種論壇問解決方案。其實筆者這裡想說,這個異常不是必須聲明的,也就是說它雖然是異常,但其實是正常運作結束的标志。 EOF 表示讀到了檔案尾,發送結束自然連接配接也就斷開了。如果這影響到了你程式的正确性的話,請各位靜下心來看看自己程式的業務邏輯,而不要把注意力狹隘的聚集在發送和接受的方法上。因為筆者也被這樣的 bug 困擾了 1 整天,被很多論壇的文章誤解了很多次最後得出的教訓。如果在 while 循環中去 readObject ,本質上是沒有問題的,有對象資料來就會讀,沒有就自動阻塞。那麼抛出 EOFException 一定是因為連接配接斷了還在繼續 read ,什麼原因導緻連接配接斷了呢?一定是業務邏輯哪裡存在錯誤,比如 NullPoint 、    ClassCaseException 、 ArrayOutofBound ,即使程式較大也沒關系,最多隻要單步調适一次就能很快發現 bug 并且解決它。

         難怪一位程式大師說過:解決問題 90 %靠經驗, 5 %靠技術,剩下 5 %靠運氣!真是金玉良言,筆者大概查閱過不下 30 篇讨論在 while 循環中使用    readObject 抛出 EOFExceptionde    的文章,大家都盲目的去關注解釋這個名詞、反序列化的行為或反對這樣寫而沒有一個人認為 EOF 是正确的行為,它其實很老實的在做它的事情。為什麼大家都忽略了真正出錯誤的地方呢?兩個字,經驗!

        2 .關于 Java 的多線程程式設計

         關于 Java 的線程,初學或者接觸不深的大概也能知道一些基本概念,同時又會很迷惑線程到底是怎麼回事?如果有人認為自己已經懂了不妨來回答下面的問題:

        a.   A 對象實作 Runnable 接口, A.start() 運作後所謂的線程對象是誰?是 A 麼?

        b.    線程的 wait() 、 notify() 方法到底是做什麼時候用的,什麼時候用?

        c.    為什麼線程的 suspend 方法會被标注過時,不推薦再使用,線程還能挂起麼?

        d.    為了同步我們會對線程方法聲明 Synchronized 來加鎖在對象上,那麼如果父類的 f() 方法加了 Synchronized ,子類重寫 f() 方法必須也加 Synchronized 麼?如果子類的 f() 方法重寫時聲明 Synchronized 并調用 super.f() ,那麼子類對象上到底有幾把鎖呢?會因為競争産生死鎖麼?

         呵呵,各位能回答上來幾道呢?如果這些都能答上來,說明對線程的概念還是滿清晰的,雖說還遠遠不能算精通。筆者這裡一一做回答,礙于篇幅的原因,筆者盡量說得簡介一點,如果大家有疑惑的歡迎一起讨論。

         首先第一點,線程跟對象完全是兩回事,雖然我們也常說線程對象。但當你用 run() 和 start() 來啟動一個線程之後,線程其實跟這個繼承了    Thread 或實作了 Runnable 的對象已經沒有關系了,對象隻能算記憶體中可用資源而對象的方法隻能算記憶體正文區可以執行的代碼段而已。既然是資源和代碼段,另外一個線程當然也可以去通路, main 函數執行就至少會啟動兩個線程,一個我們稱之為主線程,還一個是垃圾收集器的線程,主線程結束就意味着程式結束,可垃圾收集器線程很可能正在工作。

         第二點, wait() 和 sleep() 類似,都是讓線程處于阻塞狀态暫停一段時間,不同之處在于 wait 會釋放目前線程占有的所有的鎖,而    sleep 不會。我們知道獲得鎖的唯一方法是進入了 Synchronized 保護代碼段,是以大家會發現隻有 Synchronized 方法中才會出現    wait ,直接寫會給警告沒有獲得目前對象的鎖。是以 notify 跟 wait 配合使用, notify 會重新把鎖還給阻塞的線程重而使其繼續執行,當有多個對象 wait 了, notify 不能确定喚醒哪一個,必經鎖隻有一把,是以一般用 notifyAll() 來讓它們自己根據優先級等競争那唯一的一把鎖,競争到的線程執行,其他線程隻要繼續 wait 。

         從前 Java 允許在一個線程之外把線程挂起,即調用 suspend 方法,這樣的操作是極不安全的。根據面向對象的思想每個對象必須對自己的行為負責,而對自己的權力進行封裝。如果任何外步對象都能使線程被挂起而阻塞的話,程式往往會出現混亂導緻崩潰,是以這樣的方法自然是被斃掉了啦。

         最後一個問題比較有意思,首先回答的是子類重寫 f() 方法可以加 Synchronized 也可以不加,如果加了而且還内部調用了 super.f   () 的話理論上是應該對同一對象加兩把鎖的,因為每次調用 Synchronized 方法都要加一把,調用子類的 f 首先就加了一把,進入方法内部調用父類的    f 又要加一把,加兩把不是互斥的麼?那麼調父類 f 加鎖不就必須永遠等待已經加的鎖釋放而造成死鎖麼?實際上是不會的,這個機制叫重進入,當父類的 f 方法試圖在本對象上再加一把鎖的時候,因為目前線程擁有這個對象的鎖,也可以了解為開啟它的鑰匙,是以同一個線程在同一對象上還沒釋放之前加第二次鎖是不會出問題的,這個鎖其實根本就沒有加,它有了鑰匙,不管加幾把還是可以進入鎖保護的代碼段,暢通無阻,是以叫重進入,我們可以簡單認為第二把鎖沒有加上去。

         總而言之, Synchronized 的本質是不讓其他線程在同一對象上再加一把鎖。

#########################################################################################################

Java 雜談(五)

         本來預計 J2se 隻講了第四篇就收尾了,可是版主厚愛把文章置頂長期讓大家浏覽讓小弟倍感責任重大,務必追求最到更好,是以關于 J2se 一些沒有提到的部分,決定再寫幾篇把常用的部分經驗全部寫出來供大家讨論切磋。這一篇準備講一講 Xml 解析包和 Java   Swing ,然後下一篇再講 java.security 包關于 Java 沙箱安全機制和 RMI 機制,再進入 J2ee 的部分,暫時就做這樣的計劃了。如果由于實習繁忙更新稍微慢了一些,希望各位見諒!   

        1 .    Java 關于 XML 的解析     

         相信大家對 XML 都不陌生,含義是可擴充标記語言。本身它也就是一個資料的載體以樹狀表現形式出現。後來慢慢的資料變成了資訊,差別是資訊可以包括可變的狀态進而針對程式寫死的做法變革為針對統一接口寫死而可變狀态作為資訊進入了 XML 中存儲。這樣改變狀态實作擴充的唯一工作是在 XML 中添加一段文本資訊就可以了,代碼不需要改動也不需要重新編譯。這個靈活性是 XML 誕生時候誰也沒想到的。   

         當然,如果接口要能提取 XML 中配置的資訊就需要程式能解析規範的 XML 檔案, Java 中當然要提高包對這個行為進行有利支援。筆者打算講到的兩個包是  org.w3c.dom 和 javax.xml.parsers 和。(大家可以浏覽一下這些包中間的接口和類定義)   

        Javax.xml.parsers 包很簡單,沒有接口,兩個工廠配兩個解析器。顯然解析 XML 是有兩種方式的: DOM 解析和 SAX 解析。本質上并沒有誰好誰不好,隻是實作的思想不一樣罷了。給一個 XML 檔案的例子:   

          <?xml   version=”1.0”   encoding=”UTF-8”   >  

          <root   >  

<child     name=”Kitty”   >    

                  A   Cat    

                  </child   >  

          </root   >  

         所謂 DOM 解析的思路是把整個樹狀圖存入記憶體中,需要那個節點隻需要在樹上搜尋就可以讀到節點的屬性,内容等,這樣的好處是所有節點皆在記憶體可以反複搜尋重複使用,缺點是需要消耗相應的記憶體空間。   

         自然 SAX 解析的思路就是為了克服 DOM 的缺點,以事件觸發為基本思路,順序的搜尋下來,碰到了 Element 之前觸發什麼事件,碰到之後做什麼動作。由于需要自己來寫觸發事件的處理方案,是以需要借助另外一個自定義的 Handler ,處于 org.xml.sax.helpers 包中。它的優點當然是不用整個包都讀入記憶體,缺點也是隻能順序搜尋,走完一遍就得重來。   

         大家很容易就能猜到,接觸到的 J2ee 架構用的是哪一種,顯然是 DOM 。因為類似 Struts , Hibernate 架構配置檔案畢竟是很小的一部配置設定置資訊,而且需要頻繁搜尋來讀取,當然會采用 DOM 方式(其實 SAX 内部也是用 DOM 采用的結構來存儲節點資訊的)。現在無論用什麼架構,還真難發現使用  SAX 來解析 XML 的技術了,如果哪位仁兄知道,請讓筆者也學習學習。   

         既然解析方式有了,那麼就需要有解析的存儲位置。不知道大家是否發現 org.w3c.dom 這個包是沒有實作類全部都是接口的。這裡筆者想說一下 Java  如何對 XML 解析是 Jdk 應該考慮的事,是它的責任。而 w3c 組織是維護定義 XML 标準的組織,是以一個 XML 結構是怎麼樣的由 w3c 說了算,它不關心  Java 如何去實作,于是乎規定了所有 XML 存儲的結構應該遵循的規則,這就是 org.w3c.dom 裡全部的接口目的所在。在筆者看來,簡單了解接口的概念就是實作者必須遵守的原則。   

         整個 XML 對應的結構叫 Document 、子元素對應的叫做 Element 、還有節點相關的 Node 、 NodeList 、 Text 、 Entity 、  CharacterData 、 CDATASection 等接口,它們都可以在 XML 的文法中間找到相對應的含義。由于這裡不是講解 XML 基本文法,就不多介紹了。如果大家感興趣,筆者也可以專門寫一篇關于 XML 的文法規則帖與大家分享一下。   

        2 .    Java   Swing  

        Swing 是一個讓人又愛又恨的東西,可愛之處在于上手很容易,較 AWT 比起來 Swing 提供的界面功能更加強大,可恨之處在于編複雜的界面工作量實在是巨大。筆者寫過超過 3000 行的 Swing 界面,感覺使用者體驗還不是那麼優秀。最近又寫過超過 6000 行的,由于功能子產品多了,整體效果還隻是一般。體會最深的就一個字:累 !    是以大家現在都陸續不怎麼用 Swing 在真正開發的項目上了,太多界面技術可以取代它了。筆者去寫也是迫于無奈組裡面大家都沒寫過,我不入地域誰入?   

         盡管 Swing 慢慢的在被人忽略,特别是随着 B/S 慢慢的在淹沒 C/S ,筆者倒是很願意站出來為 Swing 正身。每一項技術的掌握絕不是為了流行時尚跟風。真正喜歡 Java 的朋友們還是應該好好體會一下 Swing ,相信在校的很多學生也很多在學習它。很可能從 Jdk   1.1 、 1.2 走過來的很多大學老師可能是最不熟悉它的。   

Swing 提供了一組輕元件統稱為 JComponent ,它們與 AWT 元件的最大差別是 JComponent 全部都是 Container ,而  Container 的特點是裡面可以裝載别的元件。在 Swing 元件中無論是 JButton 、 JLabel 、 JPanel 、 JList 等都可以再裝入任何其他元件。好處是程式員可以對 Swing 元件實作 “ 再開發 ” ,針對特定需求建構自己的按鈕、标簽、畫闆、清單之類的特定元件。   

         有輕自然就有重,那麼輕元件和重元件差別是?重元件表現出來的形态因作業系統不同而異,輕元件是 Swing 自己提供 GUI ,在跨平台的時候最大程度的保持一緻。   

那麼在程式設計的時候要注意一些什麼呢?筆者談談自己的幾點經驗:   

        a.    明确一個概念,隻有 Frame 元件才可以單獨顯示的,也許有人會說 JOptionPane 裡面的靜态方法就實作了單獨視窗出現,但追尋源代碼會發現其實作實出來的 Dialog 也需要依托一個 Frame 窗體,如果沒有指定就會預設産生一個然後裝載這個 Dialog 顯示出來。   

        b.   JFrame 是由這麼幾部分組成:   

                 最底下一層 JRootPane ,上面是 glassPane   ( 一個 JPanel) 和 layeredPane   ( 一個 JLayeredPane) ,而 layeredPane 又由 contentPane( 一個 JPanel) 和 menuBar 構成。我們的元件都是加在  contentPane 上,而背景圖檔隻能加在 layeredPane 上面。     至于 glassPane 是一個透明的覆寫了 contentPane 的一層,在特定效果中将被利用到來記錄滑鼠坐标或掩飾元件。   

        c.    為了增強使用者體驗,我們會在一些按鈕上添加快捷鍵,但 Swing 裡面通常隻能識别鍵盤的 Alt 鍵,要加入其他的快捷鍵,必須自己實作一個 ActionListener 。   

        d.    通過 setLayout(null) 可以使得所有元件以 setBounds() 的四個參數來精确定位各自的大小、位置,但不推薦使用,因為好的程式設計風格不應該在 Swing 代碼中寫死具體數字,所有的數字應該以常數的形式統一存在一個靜态無執行個體資源類檔案中。這個靜态無執行個體類統一負責 Swing 界面的風格,包括字型和顔色都應該包括進去。   

        e.    好的界面設計有一條 Golden   Rule:    使用者不用任何手冊通過少數嘗試就能學會使用軟體。是以盡量把按鈕以菜單的形式(不管是右鍵菜單還是窗體自帶頂部菜單)呈現給顧客,除非是頻繁點選的按鈕才有必要直接呈現在界面中。   

         其實 Swing 的功能是相當強大的,隻是現在應用不廣泛,專門去研究大概是要花不少時間的。筆者在各網站論壇浏覽關于 Swing 的技巧文章還是比較可信的,自己所學非常有限,各人體會對 Swing 各個元件的掌握就是一個實踐積累的過程。筆者隻用到過以上這些,是以隻能談談部分想法,還望大家見諒!    

###################################################################################################################

Java 雜談(六)

         這篇是筆者打算寫的 J2se 部分的最後一篇了,這篇結束之後,再寫 J2ee 部分,不知道是否還合适寫在這個版塊?大家可以給點意見,謝謝大家對小弟這麼鼓勵一路寫完前六篇 Java 雜談的 J2se 部分。最後這篇打算談一談 Java 中的 RMI 機制和 JVM 沙箱安全架構。   

        1 .    Java 中的 RMI 機制   

        RMI 的全稱是遠端方法調用,相信不少朋友都聽說過,基本的思路可以用一個經典比方來解釋: A 計算機想要計算一個兩個數的加法,但 A 自己做不了,于是叫另外一台計算機 B 幫忙, B 有計算加法的功能, A 調用它就像調用這個功能是自己的一樣友善。這個就叫做遠端方法調用了。   

         遠端方法調用是 EJB 實作的支柱,建立分布式應用的核心思想。這個很好了解,再拿上面的計算加法例子, A 隻知道去 call 計算機 B 的方法,自己并沒有 B 的那些功能,是以 A 計算機端就無法看到 B 執行這段功能的過程和代碼,因為看都看不到,是以既沒有機會竊取也沒有機會去改動方法代碼。 EJB 正式基于這樣的思想來完成它的任務的。當簡單的加法變成複雜的資料庫操作和電子商務交易應用的時候,這樣的安全性和分布式應用的便利性就表現出來優勢了。   

         好了,回到細節上,要如何實作遠端方法調用呢?我希望大家學習任何技術的時候可以試着依賴自己的下意識判斷,隻要你的想法是合理健壯的,那麼很可能實際上它就是這麼做的,畢竟真理都蘊藏在平凡的生活細節中。這樣隻要帶着一些薄弱的 Java 基礎來思考 RMI ,其實也可以想出個大概來。   

        a)    需要有一個伺服器角色,它擁有真正的功能代碼方法。例如 B ,它提供加法服務   

        b)    如果想遠端使用 B 的功能,需要知道 B 的 IP 位址   

        c)    如果想遠端使用 B 的功能,還需要知道 B 中那個特定服務的名字   

         我們很自然可以想到這些,雖然不完善,但已經很接近正确的做法了。實際上 RMI 要得以實作還得意于 Java 一個很重要的特性,就是 Java 反射機制。我們需要知道服務的名字,但又必須隐藏實作的代碼,如何去做呢?答案就是:接口!   

         舉個例子:   

        public   interface   Person(){  

public   void   sayHello();  

        }  

        Public   class   PersonImplA   implements   Person{  

public   PersonImplA(){}  

public   void   sayHello(){     System.out.println(“Hello!”);}  

        }  

        Public   class   PersonImplB   implements   Person{  

public   PersonImplB(){}  

public   void   sayHello(){     System.out.println(“Nice   to   meet   you!”);}  

        }  

         用戶端: Person   p   =   Naming.lookup(“PersonService”);  

      p.sayHello();  

         就這幾段代碼就包含了幾乎所有的實作技術,大家相信麼?用戶端請求一個 say   hello 服務,伺服器運作時接到這個請求,利用 Java 反射機制的 Class.newInstance() 傳回一個對象,但用戶端不知道伺服器傳回的是  ImplA 還是 ImplB ,它接受用的參數簽名是 Person ,它知道實作了 Person 接口的對象一定有 sayHello() 方法,這就意味着用戶端并不知道伺服器真正如何去實作的,但它通過了解 Person 接口明确了它要用的服務方法名字叫做 sayHello() 。   

         如此類推,伺服器隻需要暴露自己的接口出來供用戶端,所有用戶端就可以自己選擇需要的服務。這就像餐館隻要拿出自己的菜單出來讓客戶選擇,就可以在背景廚房一道道的按需做出來,它怎麼做的通常是不讓客戶知道的!(祖傳菜單吧, ^_^ )   

         最後一點是我調用 lookup ,查找一個叫 PersonService 名字的對象,伺服器隻要看到這個名字,在自己的目錄(相當于電話簿)中找到對應的對象名字提供服務就可以了,這個目錄就叫做 JNDI   (Java 命名與目錄接口),相信大家也聽過的。   

         有興趣的朋友不妨自己做個 RMI 的應用,很多前輩的部落格中有簡單的例子。提示一下利用 Jdk 的 bin 目錄中 rmi.exe 和  rmiregistry.exe 兩個指令就可以自己建起一個伺服器,提供遠端服務。因為例子很容易找,我就不自己舉例子了!   

        2 .    JVM 沙箱 & 架構   

        RMI 羅唆得太多了,實在是盡力想把它說清楚,希望對大家有幫助。最後的最後,給大家簡單講一下 JVM 架構,我們叫做 Java 沙箱。 Java 沙箱的基本元件如下:   

        a)    類裝載器結構       

        b)   class 檔案檢驗器       

        c)    内置于 Java 虛拟機的安全特性   

        d)    安全管理器及 Java   API  

       其中類裝載器在 3 個方面對 Java 沙箱起作用:   

        a.    它防止惡意代碼去幹涉善意的代碼   

        b.    它守護了被信任的類庫邊界   

        c.    它将代碼歸入保護域,确定了代碼可以進行哪些操作   

         虛拟機為不同的類加載器載入的類提供不同的命名空間,命名空間由一系列唯一的名稱組成,每一個被裝載的類将有一個名字,這個命名空間是由 Java 虛拟機為每一個類裝載器維護的,它們互相之間甚至不可見。   

         我們常說的包( package )是在 Java 虛拟機第 2 版的規範第一次出現,正确定義是由同一個類裝載器裝載的、屬于同一個包、多個類型的集合。類裝載器采用的機制是雙親委派模式。具體的加載器架構我在 Java 雜談(一)中已經解釋過了,當時說最外層的加載器是 AppClassLoader ,其實算上網絡層的話 AppClassLoader 也可以作為 parent ,還有更外層的加載器 URLClassLoader 。為了防止惡意攻擊由 URL 加載進來的類檔案我們當然需要分不同的通路命名空間,并且制定最安全的加載次序,簡單來說就是兩點:   

        a.    從最内層 JVM 自帶類加載器開始加載,外層惡意同名類得不到先加載而無法使用   

        b.    由于嚴格通過包來區分了通路域,外層惡意的類通過内置代碼也無法獲得權限通路到内層類,破壞代碼就自然無法生效。   

         附:關于 Java 的平台無關性,有一個例子可以很明顯的說明這個特性:   

         一般來說, C 或 C++ 中的 int 占位寬度是根據目标平台的字長來決定的,這就意味着針對不同的平台編譯同一個 C++ 程式在運作時會有不同的行為。然而對于  Java 中的 int 都是 32 位的二進制補碼辨別的有符号整數,而 float 都是遵守 IEEE   754 浮點标準的 32 位浮點數。   

        PS:    這個小弟最近也沒時間繼續研究下去了,隻是想抛磚引玉的提供給大家一個初步認識 JVM 的印象。有機會了解一下 JVM 的内部結構對今後做 Java 開發是很有好處的。

####################################################################################################################

Java 雜談(七)--接口 &        元件、容器

         終于又靜下來繼續寫這個主題的續篇,前六篇主要講了一些 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