licheng700

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            26 Posts :: 5 Stories :: 5 Comments :: 1 Trackbacks

          2005年9月19日 #

           

          jungleford如是說

              已經有一個多月沒有搭理blog了,原因很多,譬如實驗室的項目正在收工,巨忙;譬如找工作及其相關的事情;而且二月份大部分時間是陪老爹老媽,家里撥號的速度可想而知……但主要還是沒有找到一個合適的topic,或者說這段時間懶了(臨畢業(yè)前期綜合癥),凈在看《漢武大帝》和歷史方面的書,還有其它亂七八糟的閑書,就是沒有認真地玩Java,哈哈!現(xiàn)在工作差不多落實了,好在不算太爛,小資青年jungleford的生活又開始步入正軌了!以上是新年里的一些廢話。
              今天稍微聊一點關于“程序狀態(tài)保存”方面的問題,我們很容易就會想到“序列化”(Serialization,有的書上又翻譯為“順序化”或者“串行化”,但“串行”一詞總是讓我聯(lián)想到通信和硬件接口,所以我更習慣于“序列化”的叫法,何況這種叫法是有來頭的,后面我會談到這個名稱的由來),當然,序列化是一種方便有效的數(shù)據(jù)存取方式,但它還有更加廣泛的應用。廣義上講,就是討論一下I/O的一些應用。

          文件I/O:文件流→序列化

          文件流
              文件操作是最簡單最直接也是最容易想到的一種方式,我們說的文件操作不僅僅是通過FileInputStream/FileOutputStream這么“裸”的方式直接把數(shù)據(jù)寫入到本地文件(像我以前寫的一個掃雷的小游戲JavaMine就是這樣保存一局的狀態(tài)的),這樣就比較“底層”了。

          主要類與方法 描述
          FileInputStream.read() 從本地文件讀取二進制格式的數(shù)據(jù)
          FileReader.read() 從本地文件讀取字符(文本)數(shù)據(jù)
          FileOutputStream.write() 保存二進制數(shù)據(jù)到本地文件
          FileWriter.write() 保存字符數(shù)據(jù)到本地文件

          XML
              和上面的單純的I/O方式相比,XML就顯得“高檔”得多,以至于成為一種數(shù)據(jù)交換的標準。以DOM方式為例,它關心的是首先在內存中構造文檔樹,數(shù)據(jù)保存在某個結點上(可以是葉子結點,也可以是標簽結點的屬性),構造好了以后一次性的寫入到外部文件,但我們只需要知道文件的位置,并不知道I/O是怎么操作的,XML操作方式可能多數(shù)人也實踐過,所以這里也只列出相關的方法,供初學者預先了解一下。主要的包是javax.xml.parsersorg.w3c.domjavax.xml.transform

          主要類與方法 描述
          DocumentBuilderFactory.newDocumentBuilder().parse() 解析一個外部的XML文件,得到一個Document對象的DOM樹
          DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() 初始化一棵DOM樹
          Document.getDocumentElement(). appendChild() 為一個標簽結點添加一個子結點
          Document.createTextNode() 生成一個字符串結點
          Node.getChildNodes() 取得某個結點的所有下一層子結點
          Node.removeChild() 刪除某個結點的子結點
          Document. getElementsByTagName() 查找所有指定名稱的標簽結點
          Document.getElementById() 查找指定名稱的一個標簽結點,如果有多個符合,則返回某一個,通常是第一個
          Element.getAttribute() 取得一個標簽的某個屬性的的值
          Element.setAttribute() 設置一個標簽的某個屬性的的值
          Element.removeAttribute() 刪除一個標簽的某個屬性
          TransformerFactory.newInstance().newTransformer().transform() 將一棵DOM樹寫入到外部XML文件

          序列化
              使用基本的文件讀寫方式存取數(shù)據(jù),如果我們僅僅保存相同類型的數(shù)據(jù),則可以用同一種格式保存,譬如在我的JavaMine中保存一個盤局時,需要保存每一個方格的坐標、是否有地雷,是否被翻開等,這些信息組合成一個“復合類型”;相反,如果有多種不同類型的數(shù)據(jù),那我們要么把它分解成若干部分,以相同類型(譬如String)保存,要么我們需要在程序中添加解析不同類型數(shù)據(jù)格式的邏輯,這就很不方便。于是我們期望用一種比較“高”的層次上處理數(shù)據(jù),程序員應該花盡可能少的時間和代碼對數(shù)據(jù)進行解析,事實上,序列化操作為我們提供了這樣一條途徑。
              序列化(Serialization)大家可能都有所接觸,它可以把對象以某種特定的編碼格式寫入或從外部字節(jié)流(即ObjectInputStream/ObjectOutputStream)中讀取。序列化一個對象非常之簡單,僅僅實現(xiàn)一下Serializable接口即可,甚至都不用為它專門添加任何方法:

          public class MySerial implements java.io.Serializable
          {
            ...
          }

          但有一個條件:即你要序列化的類當中,它的每個屬性都必須是是“可序列化”的。這句話說起來有點拗口,其實所有基本類型(就是int,char,boolean之類的)都是“可序列化”的,而你可以看看JDK文檔,會發(fā)現(xiàn)很多類其實已經實現(xiàn)了Serializable(即已經是“可序列化”的了),于是這些類的對象以及基本數(shù)據(jù)類型都可以直接作為你需要序列化的那個類的內部屬性。如果碰到了不是“可序列化”的屬性怎么辦?對不起,那這個屬性的類還需要事先實現(xiàn)Serializable接口,如此遞歸,直到所有屬性都是“可序列化”的

          主要類與方法 描述
          ObjectOutputStream.writeObject() 將一個對象序列化到外部字節(jié)流
          ObjectInputStream.readObject() 從外部字節(jié)流讀取并重新構造對象

              從實際應用上看來,“Serializable”這個接口并沒有定義任何方法,仿佛它只是一個標記(或者說像是Java的關鍵字)而已,一旦虛擬機看到這個“標記”,就會嘗試調用自身預定義的序列化機制,除非你在實現(xiàn)Serializable接口的同時還定義了私有的readObject()或writeObject()方法。這一點很奇怪。不過你要是不愿意讓系統(tǒng)使用缺省的方式進行序列化,那就必須定義上面提到的兩個方法:

          public class MySerial implements java.io.Serializable
          {
            private void writeObject(java.io.ObjectOutputStream out) throws IOException
            {
              ...
            }
            private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
            {
              ...
            }
            ...
          }

              譬如你可以在上面的writeObject()里調用默認的序列化方法ObjectOutputStream.defaultWriteObject();譬如你不愿意將某些敏感的屬性和信息序列化,你也可以調用ObjectOutputStream.writeObject()方法明確指定需要序列化那些屬性。關于用戶可定制的序列化方法,我們將在后面提到。

          Bean
              上面的序列化只是一種基本應用,你把一個對象序列化到外部文件以后,用notepad打開那個文件,只能從為數(shù)不多的一些可讀字符中猜到這是有關這個類的信息文件,這需要你熟悉序列化文件的字節(jié)編碼方式,那將是比較痛苦的(在《Core Java 2》第一卷里提到了相關編碼方式,有興趣的話可以查看參考資料),某些情況下我們可能需要被序列化的文件具有更好的可讀性。另一方面,作為Java組件的核心概念“JavaBeans”,從JDK 1.4開始,其規(guī)范里也要求支持文本方式的“長期的持久化”(long-term persistence)。
              打開JDK文檔java.beans包里的有一個名為“Encoder”的類,這就是一個可以序列化bean的實用類。和它相關的兩個主要類有XMLEcoderXMLDecoder,顯然,這是以XML文件的格式保存和讀取bean的工具。他們的用法也很簡單,和上面ObjectOutputStream/ObjectInputStream比較類似。

          主要類與方法 描述
          XMLEncoder.writeObject() 將一個對象序列化到外部字節(jié)流
          XMLDecoder.readObject() 從外部字節(jié)流讀取并重新構造對象

              如果一個bean是如下格式:

          public class MyBean
          {
            int i;
            char[] c;
            String s;
            ...(get和set操作省略)...
          }

          那么通過XMLEcoder序列化出來的XML文件具有這樣的形式:

          <?xml version="1.0" encoding="UTF-8"?>
          <java version="1.4.0" class="java.beans.XMLDecoder">
            <object class="MyBean">
              <void property="i">
                <int>1</int>
              </void>
              <void property="c">
                <array class="char" length="3">
                  <void index="0">
                    <int>a</int>
                  </void>
                  <void index="1">
                    <int>b</int>
                  </void>
                  <void index="2">
                    <int>c</int>
                  </void>
                </array>
              </void>
              <void property="s">
                <string>fox jump!</string>
              </void>
            </object>
          </java>

              像AWTSwing中很多可視化組件都是bean,當然也是可以用這種方式序列化的,下面就是從JDK文檔中摘錄的一個JFrame序列化以后的XML文件:

          <?xml version="1.0" encoding="UTF-8"?>
          <java version="1.0" class="java.beans.XMLDecoder">
            <object class="javax.swing.JFrame">
              <void property="name">
                <string>frame1</string>
              </void>
              <void property="bounds">
                <object class="java.awt.Rectangle">
                  <int>0</int>
                  <int>0</int>
                  <int>200</int>
                  <int>200</int>
                </object>
              </void>
              <void property="contentPane">
                <void method="add">
                  <object class="javax.swing.JButton">
                    <void property="label">
                      <string>Hello</string>
                    </void>
                  </object>
                </void>
              </void>
              <void property="visible">
                <boolean>true</boolean>
              </void>
            </object>
          </java>

              因此但你想要保存的數(shù)據(jù)是一些不是太復雜的類型的話,把它做成bean再序列化也不失為一種方便的選擇。

          Properties
              在以前我總結的一篇關于集合框架的小文章里提到過,Properties是歷史集合類的一個典型的例子,這里主要不是介紹它的集合特性。大家可能都經常接觸一些配置文件,如Windows的ini文件,Apache的conf文件,還有Java里的properties文件等,這些文件當中的數(shù)據(jù)以“關鍵字-值”對的方式保存。“環(huán)境變量”這個概念都知道吧,它也是一種“key-value”對,以前也常常看到版上問“如何取得系統(tǒng)某某信息”之類的問題,其實很多都保存在環(huán)境變量里,只要用一條

          System.getProperties().list(System.out);

          就能獲得全部環(huán)境變量的列表:

          -- listing properties --
          java.runtime.name=Java(TM) 2 Runtime Environment, Stand...
          sun.boot.library.path=C:\Program Files\Java\j2re1.4.2_05\bin
          java.vm.version=1.4.2_05-b04
          java.vm.vendor=Sun Microsystems Inc.
          java.vendor.url=http://java.sun.com/
          path.separator=;
          java.vm.name=Java HotSpot(TM) Client VM
          file.encoding.pkg=sun.io
          user.country=CN
          sun.os.patch.level=Service Pack 1
          java.vm.specification.name=Java Virtual Machine Specification
          user.dir=d:\my documents\項目\eclipse\SWTDemo
          java.runtime.version=1.4.2_05-b04
          java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
          java.endorsed.dirs=C:\Program Files\Java\j2re1.4.2_05\li...
          os.arch=x86
          java.io.tmpdir=C:\DOCUME~1\cn2lx0q0\LOCALS~1\Temp\
          line.separator=

          java.vm.specification.vendor=Sun Microsystems Inc.
          user.variant=
          os.name=Windows XP
          sun.java2d.fontpath=
          java.library.path=C:\Program Files\Java\j2re1.4.2_05\bi...
          java.specification.name=Java Platform API Specification
          java.class.version=48.0
          java.util.prefs.PreferencesFactory=java.util.prefs.WindowsPreferencesFac...
          os.version=5.1
          user.home=D:\Users\cn2lx0q0
          user.timezone=
          java.awt.printerjob=sun.awt.windows.WPrinterJob
          file.encoding=GBK
          java.specification.version=1.4
          user.name=cn2lx0q0
          java.class.path=d:\my documents\項目\eclipse\SWTDemo\bi...
          java.vm.specification.version=1.0
          sun.arch.data.model=32
          java.home=C:\Program Files\Java\j2re1.4.2_05
          java.specification.vendor=Sun Microsystems Inc.
          user.language=zh
          awt.toolkit=sun.awt.windows.WToolkit
          java.vm.info=mixed mode
          java.version=1.4.2_05
          java.ext.dirs=C:\Program Files\Java\j2re1.4.2_05\li...
          sun.boot.class.path=C:\Program Files\Java\j2re1.4.2_05\li...
          java.vendor=Sun Microsystems Inc.
          file.separator=\
          java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport...
          sun.cpu.endian=little
          sun.io.unicode.encoding=UnicodeLittle
          sun.cpu.isalist=pentium i486 i386


          主要類與方法 描述
          load() 從一個外部流讀取屬性
          store() 將屬性保存到外部流(特別是文件)
          getProperty() 取得一個指定的屬性
          setProperty() 設置一個指定的屬性
          list() 列出這個Properties對象包含的全部“key-value”對
          System.getProperties() 取得系統(tǒng)當前的環(huán)境變量

              你可以這樣保存一個properties文件:

          Properties prop = new Properties();
          prop.setProperty("key1", "value1");
          ...
          FileOutputStream out = new FileOutputStream("config.properties");
          prop.store(out, "--這里是文件頭,可以加入注釋--");

          Preferences
              如果我說Java里面可以不使用JNI的手段操作Windows的注冊表你信不信?很多軟件的菜單里都有“Setting”或“Preferences”這樣的選項用來設定或修改軟件的配置,這些配置信息可以保存到一個像上面所述的配置文件當中,如果是Windows平臺下,也可能會保存到系統(tǒng)注冊表中。從JDK 1.4開始,Java在java.util下加入了一個專門處理用戶和系統(tǒng)配置信息的java.util.prefs包,其中一個類Preferences是一種比較“高級”的玩意。從本質上講,Preferences本身是一個與平臺無關的東西,但不同的OS對它的SPI(Service Provider Interface)的實現(xiàn)卻是與平臺相關的,因此,在不同的系統(tǒng)中你可能看到首選項保存為本地文件、LDAP目錄項、數(shù)據(jù)庫條目等,像在Windows平臺下,它就保存到了系統(tǒng)注冊表中。不僅如此,你還可以把首選項導出為XML文件或從XML文件導入。

          主要類與方法 描述
          systemNodeForPackage() 根據(jù)指定的Class對象得到一個Preferences對象,這個對象的注冊表路徑是從“HKEY_LOCAL_MACHINE\”開始的
          systemRoot() 得到以注冊表路徑HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs 為根結點的Preferences對象
          userNodeForPackage() 根據(jù)指定的Class對象得到一個Preferences對象,這個對象的注冊表路徑是從“HKEY_CURRENT_USER\”開始的
          userRoot() 得到以注冊表路徑HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs 為根結點的Preferences對象
          putXXX() 設置一個屬性的值,這里XXX可以為基本數(shù)值型類型,如int、long等,但首字母大寫,表示參數(shù)為相應的類型,也可以不寫而直接用put,參數(shù)則為字符串
          getXXX() 得到一個屬性的值
          exportNode() 將全部首選項導出為一個XML文件
          exportSubtree() 將部分首選項導出為一個XML文件
          importPreferences() 從XML文件導入首選項

              你可以按如下步驟保存數(shù)據(jù):

          Preferences myPrefs1 = Preferences.userNodeForPackage(this);// 這種方法是在“HKEY_CURRENT_USER\”下按當前類的路徑建立一個注冊表項
          Preferences myPrefs2 = Preferences.systemNodeForPackage(this);// 這種方法是在“HKEY_LOCAL_MACHINE\”下按當前類的路徑建立一個注冊表項
          Preferences myPrefs3 = Preferences.userRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個注冊表項
          Preferences myPrefs4 = Preferences.systemRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個注冊表項
          myPrefs1.putInt("key1", 10);
          myPrefs1.putDouble("key2", -7.15);
          myPrefs1.put("key3", "value3");
          FileOutputStream out = new FileOutputStream("prefs.xml");
          myPrefs1.exportNode(out);

          網絡I/O:Socket→RMI

          Socket
              Socket編程可能大家都很熟,所以就不多討論了,只是說通過socket把數(shù)據(jù)保存到遠端服務器或從網絡socket讀取數(shù)據(jù)也不失為一種值得考慮的方式。

          RMI
              RMI機制其實就是RPC(遠程過程調用)的Java版本,它使用socket作為基本傳輸手段,同時也是序列化最重要的一個應用。現(xiàn)在網絡傳輸從編程的角度來看基本上都是以流的方式操作,socket就是一個例子,將對象轉換成字節(jié)流的一個重要目標就是為了方便網絡傳輸。
              想象一下傳統(tǒng)的單機環(huán)境下的程序設計,對于Java語言的函數(shù)(方法)調用(注意與C語言函數(shù)調用的區(qū)別)的參數(shù)傳遞,會有兩種情況:如果是基本數(shù)據(jù)類型,這種情況下和C語言是一樣的,采用值傳遞方式;如果是對象,則傳遞的是對象的引用,包括返回值也是引用,而不是一個完整的對象拷貝!試想一下在不同的虛擬機之間進行方法調用,即使是兩個完全同名同類型的對象他們也很可能是不同的引用!此外對于方法調用過程,由于被調用過程的壓棧,內存“現(xiàn)場”完全被被調用者占有,當被調用方法返回時,才將調用者的地址寫回到程序計數(shù)器(PC),恢復調用者的狀態(tài),如果是兩個虛擬機,根本不可能用簡單壓棧的方式來保存調用者的狀態(tài)。因為種種原因,我們才需要建立RMI通信實體之間的“代理”對象,譬如“存根”就相當于遠程服務器對象在客戶機上的代理,stub就是這么來的,當然這是后話了。
              本地對象與遠程對象(未必是物理位置上的不同機器,只要不是在同一個虛擬機內皆為“遠程”)之間傳遞參數(shù)和返回值,可能有這么幾種情形:

          • 值傳遞:這又包括兩種子情形:如果是基本數(shù)據(jù)類型,那么都是“可序列化”的,統(tǒng)統(tǒng)序列化成可傳輸?shù)淖止?jié)流;如果是對象,而且不是“遠程對象”(所謂“遠程對象”是實現(xiàn)了java.rmi.Remote接口的對象),本來對象傳遞的應該是引用,但由于上述原因,引用是不足以證明對象身份的,所以傳遞的仍然是一個序列化的拷貝(當然這個對象也必須滿足上述“可序列化”的條件)。
          • 引用傳遞:可以引用傳遞的只能是“遠程對象”。這里所謂的“引用”不要理解成了真的只是一個符號,它其實是一個留在(客戶機)本地stub中的,和遠端服務器上那個真實的對象張得一模一樣的鏡像而已!只是因為它有點“特權”(不需要經過序列化),在本地內存里已經有了一個實例,真正引用的其實是這個“孿生子”。
              由此可見,序列化在RMI當中占有多么重要的地位。

          數(shù)據(jù)庫I/O:CMP、Hibernate

          什么是“Persistence”
              用過VMWare的朋友大概都知道當一個guest OS正在運行的時候點擊“Suspend”將虛擬OS掛起,它會把整個虛擬內存的內容保存到磁盤上,譬如你為虛擬OS分配了128M的運行內存,那掛起以后你會在虛擬OS所在的目錄下找到一個同樣是128M的文件,這就是虛擬OS內存的完整鏡像!這種內存的鏡像手段其實就是“Persistence”(持久化)概念的由來。

          CMP和Hibernate
              因為我對J2EE的東西不是太熟悉,隨便找了點材料看看,所以擔心說的不到位,這次就不作具體總結了,人要學習……真是一件痛苦的事情 

          序列化再探討

              從以上技術的討論中我們不難體會到,序列化是Java之所以能夠出色地實現(xiàn)其鼓吹的兩大賣點——分布式(distributed)和跨平臺(OS independent)的一個重要基礎。TIJ(即“Thinking in Java”)談到I/O系統(tǒng)時,把序列化稱為“l(fā)ightweight persistence”——“輕量級的持久化”,這確實很有意思。

          為什么叫做“序列”化?
              開場白里我說更習慣于把“Serialization”稱為“序列化”而不是“串行化”,這是有原因的。介紹這個原因之前先回顧一些計算機基本的知識,我們知道現(xiàn)代計算機的內存空間都是線性編址的(什么是“線性”知道吧,就是一個元素只有一個唯一的“前驅”和唯一的“后繼”,當然頭尾元素是個例外;對于地址來說,它的下一個地址當然不可能有兩個,否則就亂套了),“地址”這個概念推廣到數(shù)據(jù)結構,就相當于“指針”,這個在本科低年級大概就知道了。注意了,既然是線性的,那“地址”就可以看作是內存空間的“序號”,說明它的組織是有順序的,“序號”或者說“序列號”正是“Serialization”機制的一種體現(xiàn)。為什么這么說呢?譬如我們有兩個對象a和b,分別是類A和B的實例,它們都是可序列化的,而A和B都有一個類型為C的屬性,根據(jù)前面我們說過的原則,C當然也必須是可序列化的。

          import java.io.*;
          ...
          class A implements Serializable
          {
            C c;
            ...
          }

          class B implements Serializable
          {
            C c;
            ...
          }

          class C implements Serializable
          {
            ...
          }

          A a;
          B b;
          C c1;
          ...

              注意,這里我們在實例化a和b的時候,有意讓他們的c屬性使用同一個C類型對象的引用,譬如c1,那么請試想一下,但我們序列化a和b的時候,它們的c屬性在外部字節(jié)流(當然可以不僅僅是文件)里保存的是一份拷貝還是兩份拷貝呢?序列化在這里使用的是一種類似于“指針”的方案:它為每個被序列化的對象標上一個“序列號”(serial number),但序列化一個對象的時候,如果其某個屬性對象是已經被序列化的,那么這里只向輸出流寫入該屬性的序列號;從字節(jié)流恢復被序列化的對象時,也根據(jù)序列號找到對應的流來恢復。這就是“序列化”名稱的由來!這里我們看到“序列化”和“指針”是極相似的,只不過“指針”是內存空間的地址鏈,而序列化用的是外部流中的“序列號鏈”
              使用“序列號”而不是內存地址來標識一個被序列化的對象,是因為從流中恢復對象到內存,其地址可能就未必是原來的地址了——我們需要的只是這些對象之間的引用關系,而不是死板的原始位置,這在RMI中就更是必要,在兩臺不同的機器之間傳遞對象(流),根本就不可能指望它們在兩臺機器上都具有相同的內存地址。

          更靈活的“序列化”:transient屬性和Externalizable
              Serializable確實很方便,方便到你幾乎不需要做任何額外的工作就可以輕松將內存中的對象保存到外部。但有兩個問題使得Serializable的威力收到束縛:
              一個是效率問題,《Core Java 2》中指出,Serializable使用系統(tǒng)默認的序列化機制會影響軟件的運行速度,因為需要為每個屬性的引用編號和查號,再加上I/O操作的時間(I/O和內存讀寫差的可是一個數(shù)量級的大小),其代價當然是可觀的。
              另一個困擾是“裸”的Serializable不可定制,傻乎乎地什么都給你序列化了,不管你是不是想這么做。其實你可以有至少三種定制序列化的選擇。其中一種前面已經提到了,就是在implements Serializable的類里面添加私有的writeObject()和readObject()方法(這種Serializable就不裸了,),在這兩個方法里,該序列化什么,不該序列化什么,那就由你說了算了,你當然可以在這兩個方法體里面分別調用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()仍然執(zhí)行默認的序列化動作(那你在代碼上不就做無用功了?呵呵),也可以用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()方法對你中意的屬性進行序列化。但虛擬機一看到你定義了這兩個方法,它就不再用默認的機制了。
              如果僅僅為了跳過某些屬性不讓它序列化,上面的動作似乎顯得麻煩,更簡單的方法是對不想序列化的屬性加上transient關鍵字,說明它是個“暫態(tài)變量”,默認序列化的時候就不會把這些屬性也塞到外部流里了。當然,你如果定義writeObject()和readObject()方法的化,仍然可以把暫態(tài)變量進行序列化。題外話,像transientviolatefinallyassert這樣的關鍵字初學者可能會不太重視,而現(xiàn)在有的公司招聘就偏偏喜歡問這樣的問題
              再一個方案就是不實現(xiàn)Serializable而改成實現(xiàn)Externalizable接口。我們研究一下這兩個接口的源代碼,發(fā)現(xiàn)它們很類似,甚至容易混淆。我們要記住的是:Externalizable默認并不保存任何對象相關信息!任何保存和恢復對象的動作都是你自己定義的。Externalizable包含兩個public的方法:

          public void writeExternal(ObjectOutput out) throws IOException;
          public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

              乍一看這和上面的writeObject()和readObject()幾乎差不多,但Serializable和Externalizable走的是兩個不同的流程:Serializable在對象不存在的情況下,就可以僅憑外部的字節(jié)序列把整個對象重建出來;但Externalizable在重建對象時,先是調用該類的默認構造函數(shù)(即不含參數(shù)的那個構造函數(shù))使得內存中先有這么一個實例,然后再調用readExternal方法對實例中的屬性進行恢復,因此,如果默認構造函數(shù)中和readExternal方法中都沒有賦值的那些屬性,特別他們是非基本類型的話,將會是空(null)。在這里需要注意的是,transient只能用在對Serializable而不是Externalizable的實現(xiàn)里面

          序列化與克隆
              從“可序列化”的遞歸定義來看,一個序列化的對象貌似對象內存映象的外部克隆,如果沒有共享引用的屬性的化,那么應該是一個深度克隆。關于克隆的話題有可以談很多,這里就不細說了,有興趣的話可以參考IBM developerWorks上的一篇文章:JAVA中的指針,引用及對象的clone

          一點啟示

              作為一個實際的應用,我在寫那個簡易的郵件客戶端JExp的時候曾經對比過好幾種保存Message對象(主要是幾個關鍵屬性和郵件的內容)到本地的方法,譬如XML、Properties等,最后還是選擇了用序列化的方式,因為這種方法最簡單, 大約可算是“學以致用”罷。這里“存取程序狀態(tài)”其實只是一個引子話題罷了,我想說的是——就如同前面我們討論的關于logging的話題一樣——在Java面前對同一個問題你可以有很多種solution:熟悉文件操作的,你可能會覺得Properties、XML或Bean比較方便,然后又發(fā)現(xiàn)了還有Preferences這么一個東東,大概又會感慨“天外有天”了,等到你接觸了很多種新方法以后,結果又會“殊途同歸”,重新反省Serialization機制本身。這不僅是Java,科學也是同樣的道理。

          posted @ 2005-09-28 10:09 小海船 閱讀(342) | 評論 (0)編輯 收藏

          1。追加文件方法
                    1.java.io.FileWriter 的構造函數(shù)中:FileWriter(File file, boolean append)
                    Constructs a FileWriter object given a File object.
          append - if true, then bytes will be written to the end of the file rather than the beginning

                   2.類java.io.RandomAccessFile extends Object implements DataOutput, DataInput 中方法
                        seek(long pos)將當前操作指針移到文件末尾。
          2.關于隔行寫入文件java.io.BufferedWriter類中的方法newLine()



          posted @ 2005-09-27 14:00 小海船 閱讀(1192) | 評論 (1)編輯 收藏

          Log4j 學習筆記

          ccjsmile (http://ijsp.net)

           

          這是我在學習Log4j時做的一點筆記,希望對各位朋友有一點幫助。我的mail:ccjsmile@sohu.com,希望能與您進行討論^_*

           

          Log4j 是一個開放源碼項目,它是一個日志管理程序。

          Log4j的優(yōu)點:

          1.       方便的調試信息;

          2.       日志以各種豐富的(主要是文件)形式保留,用于以后分析;

          缺點:減慢程序運行速度.


          (A) 其中,level 是日志記錄的優(yōu)先級,分為OFFFATALERRORWARNINFODEBUGALL或者您定義的級別。Log4j建議只使用四個級別,優(yōu)先級從高到低分別是ERRORWARNINFODEBUG。通過在這里定義的級別,您可以控制到應用程序中相應級別的日志信息的開關。比如在這里定義了INFO級別,則應用程序中所有DEBUG級別的日志信息將不被打印出來。

          appenderName就是指定日志信息輸出到哪個地方。您可以同時指定多個輸出目的地。

           

          (B) 其中,Log4j提供的appender有以下幾種:

          org.apache.log4j.ConsoleAppender(控制臺),

          org.apache.log4j.FileAppender(文件),

          org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件),org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件),

          org.apache.log4j.WriterAppender(將日志信息以流格式發(fā)送到任意指定的地方)

           

          (C) 其中,Log4j提供的layout有以下幾種:

          org.apache.log4j.HTMLLayout(以HTML表格形式布局),

          org.apache.log4j.PatternLayout(可以靈活地指定布局模式),

          org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串),

          org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等等信息)

           

           

          下面介紹一下log4jweb中應用的例子:

          這是一個用于log4j初始化的servlet

           

          package net.ijsp.log4j;

           

          import org.apache.log4j.PropertyConfigurator;

          import javax.servlet.http.HttpServlet;

          import javax.servlet.ServletException;

           

          public class InitLog4j extends HttpServlet {

           

            public  void init() throws ServletException  {

             PropertyConfigurator.configure("D:/resin/webapps/log4j/web-inf/classes/log4j.properties");

              System.out.println("ok");

            }

          }

           

          在上述文件中我們發(fā)現(xiàn)需要一個log4j.properties的文件,他的存放路徑為:D:/resin/webapps/log4j/web-inf/classes/log4j.properties

          這個properties的文件內容如下:

          #log4j.properties

          #Set root logger level to DEBUG and its only appender to A1.

          log4j.rootLogger=INFO,A1

          #A1 is set to be a ConsoleAppender.

          #log4j.appender.A1=org.apache.log4j.ConsoleAppender

                 log4j.appender.A1=org.apache.log4j.RollingFileAppender

                 log4j.appender.A1.File=example11.log

          #A1 uses PatternLayout

                 log4j.appender.A1.layout=org.apache.log4j.PatternLayout

                 log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c \n- %m%n\n"

           

          log4j.logger.ltestlog4j=INFO,A2

          log4j.appender.A2=org.apache.log4j.ConsoleAppender

                 log4j.appender.A2.layout=org.apache.log4j.PatternLayout

                 log4j.appender.A2.layout.ConversionPattern=%d [%t] %-5p %c \n- %m%n\n"

           

          #log4j.appender.A1.MaxFileSize=1000KB

          # Keep one backup file

          #log4j.appender.A1.MaxBackupIndex=1

           

          因為這是一個servlet文件,同時我們還要修改web.xml文件

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

          <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

          <web-app>

                 <servlet>

                        <servlet-name>log4jinit</servlet-name>

                        <servlet-class>net.ijsp.log4j.InitLog4j</servlet-class>

                        <load-on-startup>1</load-on-startup>

            </servlet>

          </web-app>

           

          下面這兩個為測試文件:

          package net.ijsp.log4j;

           

          import org.apache.log4j.PropertyConfigurator;

          import org.apache.log4j.Logger;

          import javax.servlet.http.HttpServlet;

          import javax.servlet.ServletException;

           

          public class Test {

           

            public Test() {}

           

            static Logger logger =Logger.getRootLogger();

            static Logger logger1 = Logger.getLogger("ltestlog4j");

           

            public void t() {

              logger.error("sssssssssss");

              System.out.println(logger);

              logger1.error("kjdlfkj");

              System.out.println("ddddddddddddddd");

            }

          }

           

           

          <%@page import ="net.ijsp.log4j.*"%>

           

          <%

          Test t = new Test();

          t.t();

          %>

          posted @ 2005-09-26 17:27 小海船 閱讀(342) | 評論 (0)編輯 收藏


          Last login: Fri Sep 23 14:37:23 2005 from 211.155.247.222
          Sun Microsystems Inc.   SunOS 5.9       Generic May 2002
          [iplanet@ec2 sunone]$ telnet 10.211.12.22
          Trying 10.211.12.22...
          Connected to 10.211.12.22.
          Escape character is '^]'.


          SunOS 5.9

          login: oracle
          Password:
          Last login: Mon Sep 26 06:39:46 from 10.211.12.233
          Sun Microsystems Inc.   SunOS 5.9       Generic May 2002
          You have mail.
          [oracle@db oracle]$ sqlplus cpf/cpf

          SQL*Plus: Release 9.2.0.5.0 - Production on Mon Sep 26 09:24:44 2005

          Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.


          Connected to:
          Oracle9i Enterprise Edition Release 9.2.0.5.0 - 64bit Production
          With the Partitioning, OLAP and Oracle Data Mining options
          JServer Release 9.2.0.5.0 - Production

          SQL> drop table bs_banksetting
            2  ;

          Table dropped.

          SQL> drop table bs_bankaccountinfo;

          Table dropped.

          SQL> drop table bs_clientsetting;

          Table dropped.

          SQL> drop table bs_countrysetting;

          Table dropped.

          SQL> drop table bs_currencysetting;

          Table dropped.

          SQL> exit
          Disconnected from Oracle9i Enterprise Edition Release 9.2.0.5.0 - 64bit Production
          With the Partitioning, OLAP and Oracle Data Mining options
          JServer Release 9.2.0.5.0 - Production
          [oracle@db oracle]$ ftp 211.155.247.197
          Connected to 211.155.247.197.
          220 fsdev FTP server ready.
          Name (211.155.247.197:oracle): root
          331 Password required for root.
          Password:
          230 User root logged in.
          Remote system type is UNIX.
          Using binary mode to transfer files.
          ftp> cd bankportal
          250 CWD command successful.
          ftp> ls
          200 PORT command successful.
          150 Opening ASCII mode data connection for file list.
          AcctDataSourceType.class
          AcctDataSourceType.java
          CurrencyMappingDAO_oracle.class
          SettingData.dmp
          account
          addp.jsp
          bankportal.properties
          create_table.sql
          datamaintain
          example.xls
          expbp0926.dmp
          expbp1420.dmp
          iTreasury-bankportal.ear
          itreasury.properties
          menubp.dmp
          model.xls
          query
          v003.jsp
          v005.jsp
          226 Transfer complete.
          316 bytes received in 0.016 seconds (19.33 Kbytes/s)
          ftp> get expbp0926.dmp
          200 PORT command successful.
          150 Opening BINARY mode data connection for expbp0926.dmp (47104 bytes).
          226 Transfer complete.
          local: expbp0926.dmp remote: expbp0926.dmp
          47104 bytes received in 0.16 seconds (292.70 Kbytes/s)
          ftp> bye
          221-You have transferred 47104 bytes in 1 files.
          221-Total traffic for this session was 48051 bytes in 2 transfers.
          221-Thank you for using the FTP service on fsdev.
          221 Goodbye.
          [oracle@db oracle]$ imp system/manager1 file=expbp0926.dmp fromuser=bp_cpf touser=cpf

          Import: Release 9.2.0.5.0 - Production on Mon Sep 26 09:30:09 2005

          Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.


          Connected to: Oracle9i Enterprise Edition Release 9.2.0.5.0 - 64bit Production
          With the Partitioning, OLAP and Oracle Data Mining options
          JServer Release 9.2.0.5.0 - Production

          Export file created by EXPORT:V09.02.00 via conventional path

          Warning: the objects were exported by BP_CPF, not by you

          import done in ZHS16GBK character set and AL16UTF16 NCHAR character set
          . importing BP_CPF's objects into CPF
          . . importing table           "BS_BANKACCOUNTINFO"        150 rows imported
          . . importing table               "BS_BANKSETTING"        107 rows imported
          . . importing table             "BS_CLIENTSETTING"         27 rows imported
          . . importing table            "BS_COUNTRYSETTING"         21 rows imported
          . . importing table           "BS_CURRENCYSETTING"         53 rows imported
          Import terminated successfully without warnings.
          [oracle@db oracle]$

          posted @ 2005-09-26 09:38 小海船 閱讀(282) | 評論 (0)編輯 收藏

          package property;

          import java.io.*;
          import java.util.*;

          public class TemplateId {

           private static Properties p;

           private static final TemplateId pi = new TemplateId();

           public TemplateId() {
            // 從templateId.properties屬性文件獲得數(shù)據(jù)
            InputStream is = getClass()
              .getResourceAsStream("templateId.properties");
            p = new Properties();
            try {
             p.load(is);
            } catch (IOException ex) {
             ex.printStackTrace();
            }
           }

           // 此處的templateId就是templateId.properties屬性文件中的templateId。
           public static String getTemplateId() {
            return pi.p.getProperty("templateId");
           }

           public static void main(String args[]) {
            System.out.println("templateId=" + getTemplateId()); // 測試調用
           }
          }
          templateId.properties文件內容:templateId=FFD4156506-3-2F8CAC7

          posted @ 2005-09-21 17:51 小海船 閱讀(364) | 評論 (0)編輯 收藏

                  所謂熱部署,就是在應用正在運行的時候升級軟件,卻不需要重新啟動應用。對于Java應用程序來說,熱部署就是在運行時更新Java類文件。在基于Java的應用服務器實現(xiàn)熱部署的過程中,類裝入器扮演著重要的角色。大多數(shù)基于Java的應用服務器,包括EJB服務器和Servlet容器,都支持熱部署。類裝入器不能重新裝入一個已經裝入的類,但只要使用一個新的類裝入器實例,就可以將類再次裝入一個正在運行的應用程序。

                 由于類裝入器擔負著把代碼裝入JVM的重任,所以它的體系結構應當保證整個平臺的安全性不會受到威脅。每一個類裝入器要為它裝入的類定義一個獨立的名稱空間,因此運行時,一個類由它的包名稱和裝入它的類裝入器兩者結合唯一地標識。

            在名稱空間之外,類不可見,運行時不同名稱空間的類之間有一種保護性的“隔離墻”,在Java 2向父類委托的類裝入模式中,類裝入器可以請求其父類裝入器裝入的類,因此類裝入器需要的類不一定全部由它自己裝入。

            在Java運行環(huán)境中,不同的類裝入器從不同的代碼庫裝入類,之所以要將各個類裝入器代碼庫的位置分開,是為了便于給不同的代碼庫設定不同的信任級別。在JVM中,由bootstrap類裝入器裝入的類具有最高的信任級別,用戶自定義類裝入器代碼庫的信任級別最低。此外,類裝入器可以把每一個裝入的類放入一個保護域,保護域定義了代碼執(zhí)行時所擁有的權限。

            如果要以系統(tǒng)安全策略(一個java.security.Policy的實例)為基礎定義代碼的權限,定制類裝入器必須擴展java.security.SecureClassLoad類,調用其defineClass方法,SecureClassLoad類的defineClass方法有一個java.security.CodeSource參數(shù)。defindClass方法從系統(tǒng)策略獲得與CodeSource關聯(lián)的權限,以此為基礎定義一個java.security.ProtectionDomain。詳細介紹該安全模型已經超出了本文的范圍,請讀者自行參閱有關JVM內部機制的資料。

           類裝入器裝入的最小執(zhí)行單元是Java .class文件。Java .class文件包含Java類的二進制描述,其中有可執(zhí)行的字節(jié)碼以及該類用到的對其他類的引用,包括對Java標準API里面的類的引用。簡單地說,類裝入器首先找到要裝入的Java類的字節(jié)碼,讀入字節(jié)碼,創(chuàng)建一個java.lang.Class類的實例。做好這些準備之后,類就可以被JVM執(zhí)行了。
           當JVM最初開始運行時,它里面不裝入任何類。如果要求JVM執(zhí)行一個程序,被執(zhí)行的類首先裝入,字節(jié)碼執(zhí)行期間會引用到其他類和接口,這些被引用到的類和接口隨之也被裝入。因此,有人把JVM的類裝入方式稱為“懶惰的”裝入方式,即只有必須用到某個類時才會裝入它(而不是預先裝入各種可能用到的類),正因為如此,開始時JVM不必知道運行時要裝入哪些類。在Java平臺上,懶惰的裝入方式是實現(xiàn)動態(tài)可擴展性機制的關鍵因素之一。在本文的后面,你將會看到通過實現(xiàn)一個定制的Java類裝入器,我們可以為Java運行時環(huán)境加入許多有趣的功能
             一、委托模式
               Java 2運行時環(huán)境中有多個類裝入器的實例,每一個類裝入器的實例從不同的代碼庫裝入Java類。例如,Java核心API類由bootstrap(或primordial)類裝入器裝入,應用程序的類由system(或application)類裝入器裝入。另外,應用程序可以自定義類裝入器從指定的代碼庫裝入類。Java 2定義了類裝入器之間的父-子關系,每一個類裝入器(bootstrap除外)都有一個父類裝入器,形成一個由類裝入器構成的樹形結構,bootstrap類裝入器是這個樹形結構的根,因此bootstrap沒有父類裝入器。
           當客戶程序請求類裝入器裝入某個類時,類裝入器按照下面的算法裝入一個類:
             首先執(zhí)行一次檢查,查看客戶程序請求的類是否已經由當前的類裝入器裝入。如果是,則返回已裝入的類,請求處理完畢。JVM緩沖了類裝入器裝入的所有類,已經裝入的類不會被再次裝入。

           如果尚未裝入類,則裝入類的請求被委托給父類裝入器,這個操作發(fā)生在當前的類裝入器嘗試裝入指定的類之前。委托裝入類的操作一直向上傳遞,直至bootstrap類裝入器,如前所述,bootstrap類裝入器是處于樹形結構的頂層,它不再有可委托的父類裝入器。

           如果父類裝入器未能裝入指定的類,則當前的類裝入器將嘗試搜索該類。每一個類裝入器有預定義的搜索和裝入類的位置。例如,bootstrap類裝入器搜索sun.boot.class.path系統(tǒng)屬性中指定的位置(包括目錄和zip、jar文件),system類裝入器搜索的位置由JVM開始運行時傳入的CLASSPATH命令行變量指定(相當于設置java.class.path系統(tǒng)屬性)。如果找到了類,則類裝入器將類返回給系統(tǒng),請求處理完畢。
             
           ⑷ 如果找不到類,則拋出java.lang.ClassNotFoundException異常。
          posted @ 2005-09-21 15:50 小海船 閱讀(1485) | 評論 (0)編輯 收藏

          在網上看見了這篇文章,作者以輕松的語言比喻了java的32種模式,有很好的啟發(fā)作用。
                  創(chuàng)建型模式
                 
                  1、FACTORY—追MM少不了請吃飯了,麥當勞的雞翅和肯德基的雞翅都是MM愛吃的東西,雖然口味有所不同,但不管你帶MM去麥當勞或肯德基,只管向服務員說“來四個雞翅”就行了。麥當勞和肯德基就是生產雞翅的Factory
                 
                  工廠模式:客戶類和工廠類分開。消費者任何時候需要某種產品,只需向工廠請求即可。消費者無須修改就可以接納新產品。缺點是當產品修改時,工廠類也要做相應的修改。如:如何創(chuàng)建及如何向客戶端提供。
                 
                  2、BUILDER—MM最愛聽的就是“我愛你”這句話了,見到不同地方的MM,要能夠用她們的方言跟她說這句話哦,我有一個多種語言翻譯機,上面每種語言都有一個按鍵,見到MM我只要按對應的鍵,它就能夠用相應的語言說出“我愛你”這句話了,國外的MM也可以輕松搞掂,這就是我的“我愛你”builder。(這一定比美軍在伊拉克用的翻譯機好賣)
                 
                  建造模式:將產品的內部表象和產品的生成過程分割開來,從而使一個建造過程生成具有不同的內部表象的產品對象。建造模式使得產品內部表象可以獨立的變化,客戶不必知道產品內部組成的細節(jié)。建造模式可以強制實行一種分步驟進行的建造過程。
                 
                  3、FACTORY METHOD—請MM去麥當勞吃漢堡,不同的MM有不同的口味,要每個都記住是一件煩人的事情,我一般采用Factory Method模式,帶著MM到服務員那兒,說“要一個漢堡”,具體要什么樣的漢堡呢,讓MM直接跟服務員說就行了。
                 
                  工廠方法模式:核心工廠類不再負責所有產品的創(chuàng)建,而是將具體創(chuàng)建的工作交給子類去做,成為一個抽象工廠角色,僅負責給出具體工廠類必須實現(xiàn)的接口,而不接觸哪一個產品類應當被實例化這種細節(jié)。
                 
                  4、PROTOTYPE—跟MM用QQ聊天,一定要說些深情的話語了,我搜集了好多肉麻的情話,需要時只要copy出來放到QQ里面就行了,這就是我的情話prototype了。(100塊錢一份,你要不要)
                 
                  原始模型模式:通過給出一個原型對象來指明所要創(chuàng)建的對象的類型,然后用復制這個原型對象的方法創(chuàng)建出更多同類型的對象。原始模型模式允許動態(tài)的增加或減少產品類,產品類不需要非得有任何事先確定的等級結構,原始模型模式適用于任何的等級結構。缺點是每一個類都必須配備一個克隆方法。
                 
                  5、SINGLETON—俺有6個漂亮的老婆,她們的老公都是我,我就是我們家里的老公Sigleton,她們只要說道“老公”,都是指的同一個人,那就是我(剛才做了個夢啦,哪有這么好的事)
                 
                  單例模式:單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例單例模式。單例模式只應在有真正的“單一實例”的需求時才可使用。
                 
                  結構型模式
                 
                  6、ADAPTER—在朋友聚會上碰到了一個美女Sarah,從香港來的,可我不會說粵語,她不會說普通話,只好求助于我的朋友kent了,他作為我和Sarah之間的Adapter,讓我和Sarah可以相互交談了(也不知道他會不會耍我)
                 
                  適配器(變壓器)模式:把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口原因不匹配而無法一起工作的兩個類能夠一起工作。適配類可以根據(jù)參數(shù)返還一個合適的實例給客戶端。
                 
                  7、BRIDGE—早上碰到MM,要說早上好,晚上碰到MM,要說晚上好;碰到MM穿了件新衣服,要說你的衣服好漂亮哦,碰到MM新做的發(fā)型,要說你的頭發(fā)好漂亮哦。不要問我“早上碰到MM新做了個發(fā)型怎么說”這種問題,自己用BRIDGE組合一下不就行了
                 
                  橋梁模式:將抽象化與實現(xiàn)化脫耦,使得二者可以獨立的變化,也就是說將他們之間的強關聯(lián)變成弱關聯(lián),也就是指在一個軟件系統(tǒng)的抽象化和實現(xiàn)化之間使用組合/聚合關系而不是繼承關系,從而使兩者可以獨立的變化。
                 
                  8、COMPOSITE—Mary今天過生日。“我過生日,你要送我一件禮物。”“嗯,好吧,去商店,你自己挑。”“這件T恤挺漂亮,買,這條裙子好看,買,這個包也不錯,買。”“喂,買了三件了呀,我只答應送一件禮物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻煩你包起來。”“……”,MM都會用Composite模式了,你會了沒有?
                 
                  合成模式:合成模式將對象組織到樹結構中,可以用來描述整體與部分的關系。合成模式就是一個處理對象的樹結構的模式。合成模式把部分與整體的關系用樹結構表示出來。合成模式使得客戶端把一個個單獨的成分對象和由他們復合而成的合成對象同等看待。
                 
                  9、DECORATOR—Mary過完輪到Sarly過生日,還是不要叫她自己挑了,不然這個月伙食費肯定玩完,拿出我去年在華山頂上照的照片,在背面寫上“最好的的禮物,就是愛你的Fita”,再到街上禮品店買了個像框(賣禮品的MM也很漂亮哦),再找隔壁搞美術設計的Mike設計了一個漂亮的盒子裝起來……,我們都是Decorator,最終都在修飾我這個人呀,怎么樣,看懂了嗎?
                 
                  裝飾模式:裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案,提供比繼承更多的靈活性。動態(tài)給一個對象增加功能,這些功能可以再動態(tài)的撤消。增加由一些基本功能的排列組合而產生的非常大量的功能。
                 
                  10、FACADE—我有一個專業(yè)的Nikon相機,我就喜歡自己手動調光圈、快門,這樣照出來的照片才專業(yè),但MM可不懂這些,教了半天也不會。幸好相機有Facade設計模式,把相機調整到自動檔,只要對準目標按快門就行了,一切由相機自動調整,這樣MM也可以用這個相機給我拍張照片了。
                 
                  門面模式:外部與一個子系統(tǒng)的通信必須通過一個統(tǒng)一的門面對象進行。門面模式提供一個高層次的接口,使得子系統(tǒng)更易于使用。每一個子系統(tǒng)只有一個門面類,而且此門面類只有一個實例,也就是說它是一個單例模式。但整個系統(tǒng)可以有多個門面類。
                 
                  11、FLYWEIGHT—每天跟MM發(fā)短信,手指都累死了,最近買了個新手機,可以把一些常用的句子存在手機里,要用的時候,直接拿出來,在前面加上MM的名字就可以發(fā)送了,再不用一個字一個字敲了。共享的句子就是Flyweight,MM的名字就是提取出來的外部特征,根據(jù)上下文情況使用。
                 
                  享元模式:FLYWEIGHT在拳擊比賽中指最輕量級。享元模式以共享的方式高效的支持大量的細粒度對象。享元模式能做到共享的關鍵是區(qū)分內蘊狀態(tài)和外蘊狀態(tài)。內蘊狀態(tài)存儲在享元內部,不會隨環(huán)境的改變而有所不同。外蘊狀態(tài)是隨環(huán)境的改變而改變的。外蘊狀態(tài)不能影響內蘊狀態(tài),它們是相互獨立的。將可以共享的狀態(tài)和不可以共享的狀態(tài)從常規(guī)類中區(qū)分開來,將不可以共享的狀態(tài)從類里剔除出去。客戶端不可以直接創(chuàng)建被共享的對象,而應當使用一個工廠對象負責創(chuàng)建被共享的對象。享元模式大幅度的降低內存中對象的數(shù)量。
                 
                  12、PROXY—跟MM在網上聊天,一開頭總是“hi,你好”,“你從哪兒來呀?”“你多大了?”“身高多少呀?”這些話,真煩人,寫個程序做為我的Proxy吧,凡是接收到這些話都設置好了自動的回答,接收到其他的話時再通知我回答,怎么樣,酷吧。
                 
                  代理模式:代理模式給某一個對象提供一個代理對象,并由代理對象控制對源對象的引用。代理就是一個人或一個機構代表另一個人或者一個機構采取行動。某些情況下,客戶不想或者不能夠直接引用一個對象,代理對象可以在客戶和目標對象直接起到中介的作用。客戶端分辨不出代理主題對象與真實主題對象。代理模式可以并不知道真正的被代理對象,而僅僅持有一個被代理對象的接口,這時候代理對象不能夠創(chuàng)建被代理對象,被代理對象必須有系統(tǒng)的其他角色代為創(chuàng)建并傳入。
                 
                  行為模式
                 
                  13、CHAIN OF RESPONSIBLEITY—晚上去上英語課,為了好開溜坐到了最后一排,哇,前面坐了好幾個漂亮的MM哎,找張紙條,寫上“Hi,可以做我的女朋友嗎?如果不愿意請向前傳”,紙條就一個接一個的傳上去了,糟糕,傳到第一排的MM把紙條傳給老師了,聽說是個老處女呀,快跑!
                 
                  責任鏈模式:在責任鏈模式中,很多對象由每一個對象對其下家的引用而接
                 
                  起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。客戶并不知道鏈上的哪一個對象最終處理這個請求,系統(tǒng)可以在不影響客戶端的情況下動態(tài)的重新組織鏈和分配責任。處理者有兩個選擇:承擔責任或者把責任推給下家。一個請求可以最終不被任何接收端對象所接受。
                 
                  14、COMMAND—俺有一個MM家里管得特別嚴,沒法見面,只好借助于她弟弟在我們倆之間傳送信息,她對我有什么指示,就寫一張紙條讓她弟弟帶給我。這不,她弟弟又傳送過來一個COMMAND,為了感謝他,我請他吃了碗雜醬面,哪知道他說:“我同時給我姐姐三個男朋友送COMMAND,就數(shù)你最小氣,才請我吃面。”,:-(
                 
                  命令模式:命令模式把一個請求或者操作封裝到一個對象中。命令模式把發(fā)出命令的責任和執(zhí)行命令的責任分割開,委派給不同的對象。命令模式允許請求的一方和發(fā)送的一方獨立開來,使得請求的一方不必知道接收請求的一方的接口,更不必知道請求是怎么被接收,以及操作是否執(zhí)行,何時被執(zhí)行以及是怎么被執(zhí)行的。系統(tǒng)支持命令的撤消。
                 
                  15、INTERPRETER—俺有一個《泡MM真經》,上面有各種泡MM的攻略,比如說去吃西餐的步驟、去看電影的方法等等,跟MM約會時,只要做一個Interpreter,照著上面的腳本執(zhí)行就可以了。
                 
                  解釋器模式:給定一個語言后,解釋器模式可以定義出其文法的一種表示,并同時提供一個解釋器。客戶端可以使用這個解釋器來解釋這個語言中的句子。解釋器模式將描述怎樣在有了一個簡單的文法后,使用模式設計解釋這些語句。在解釋器模式里面提到的語言是指任何解釋器對象能夠解釋的任何組合。在解釋器模式中需要定義一個代表文法的命令類的等級結構,也就是一系列的組合規(guī)則。每一個命令對象都有一個解釋方法,代表對命令對象的解釋。命令對象的等級結構中的對象的任何排列組合都是一個語言。
                 
                 
                 
                  16、ITERATOR—我愛上了Mary,不顧一切的向她求婚。
                 
                  Mary:“想要我跟你結婚,得答應我的條件”
                 
                  我:“什么條件我都答應,你說吧”
                 
                  Mary:“我看上了那個一克拉的鉆石”
                 
                  我:“我買,我買,還有嗎?”
                 
                  Mary:“我看上了湖邊的那棟別墅”
                 
                  我:“我買,我買,還有嗎?”
                 
                  Mary:“你的小弟弟必須要有50cm長”
                 
                  我腦袋嗡的一聲,坐在椅子上,一咬牙:“我剪,我剪,還有嗎?”
                 
                  ……
                 
                  迭代子模式:迭代子模式可以順序訪問一個聚集中的元素而不必暴露聚集的內部表象。多個對象聚在一起形成的總體稱之為聚集,聚集對象是能夠包容一組對象的容器對象。迭代子模式將迭代邏輯封裝到一個獨立的子對象中,從而與聚集本身隔開。迭代子模式簡化了聚集的界面。每一個聚集對象都可以有一個或一個以上的迭代子對象,每一個迭代子的迭代狀態(tài)可以是彼此獨立的。迭代算法可以獨立于聚集角色變化。
                 
                  17、MEDIATOR—四個MM打麻將,相互之間誰應該給誰多少錢算不清楚了,幸虧當時我在旁邊,按照各自的籌碼數(shù)算錢,賺了錢的從我這里拿,賠了錢的也付給我,一切就OK啦,俺得到了四個MM的電話。
                 
                  調停者模式:調停者模式包裝了一系列對象相互作用的方式,使得這些對象不必相互明顯作用。從而使他們可以松散偶合。當某些對象之間的作用發(fā)生改變時,不會立即影響其他的一些對象之間的作用。保證這些作用可以彼此獨立的變化。調停者模式將多對多的相互作用轉化為一對多的相互作用。調停者模式將對象的行為和協(xié)作抽象化,把對象在小尺度的行為上與其他對象的相互作用分開處理。
                 
                  18、MEMENTO—同時跟幾個MM聊天時,一定要記清楚剛才跟MM說了些什么話,不然MM發(fā)現(xiàn)了會不高興的哦,幸虧我有個備忘錄,剛才與哪個MM說了什么話我都拷貝一份放到備忘錄里面保存,這樣可以隨時察看以前的記錄啦。
                 
                  備忘錄模式:備忘錄對象是一個用來存儲另外一個對象內部狀態(tài)的快照的對象。備忘錄模式的用意是在不破壞封裝的條件下,將一個對象的狀態(tài)捉住,并外部化,存儲起來,從而可以在將來合適的時候把這個對象還原到存儲起來的狀態(tài)。
                 
                  19、OBSERVER—想知道咱們公司最新MM情報嗎?加入公司的MM情報郵件組就行了,tom負責搜集情報,他發(fā)現(xiàn)的新情報不用一個一個通知我們,直接發(fā)布給郵件組,我們作為訂閱者(觀察者)就可以及時收到情報啦
                 
                  觀察者模式:觀察者模式定義了一種一隊多的依賴關系,讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個主題對象在狀態(tài)上發(fā)生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。
                 
                  20、STATE—跟MM交往時,一定要注意她的狀態(tài)哦,在不同的狀態(tài)時她的行為會有不同,比如你約她今天晚上去看電影,對你沒興趣的MM就會說“有事情啦”,對你不討厭但還沒喜歡上的MM就會說“好啊,不過可以帶上我同事么?”,已經喜歡上你的MM就會說“幾點鐘?看完電影再去泡吧怎么樣?”,當然你看電影過程中表現(xiàn)良好的話,也可以把MM的狀態(tài)從不討厭不喜歡變成喜歡哦。
                 
                  狀態(tài)模式:狀態(tài)模式允許一個對象在其內部狀態(tài)改變的時候改變行為。這個對象看上去象是改變了它的類一樣。狀態(tài)模式把所研究的對象的行為包裝在不同的狀態(tài)對象里,每一個狀態(tài)對象都屬于一個抽象狀態(tài)類的一個子類。狀態(tài)模式的意圖是讓一個對象在其內部狀態(tài)改變的時候,其行為也隨之改變。狀態(tài)模式需要對每一個系統(tǒng)可能取得的狀態(tài)創(chuàng)立一個狀態(tài)類的子類。當系統(tǒng)的狀態(tài)變化時,系統(tǒng)便改變所選的子類。
                 
                  21、STRATEGY—跟不同類型的MM約會,要用不同的策略,有的請電影比較好,有的則去吃小吃效果不錯,有的去海邊浪漫最合適,單目的都是為了得到MM的芳心,我的追MM錦囊中有好多Strategy哦。
                 
                  策略模式:策略模式針對一組算法,將每一個算法封裝到具有共同接口的獨立的類中,從而使得它們可以相互替換。策略模式使得算法可以在不影響到客戶端的情況下發(fā)生變化。策略模式把行為和環(huán)境分開。環(huán)境類負責維持和查詢行為類,各種算法在具體的策略類中提供。由于算法和環(huán)境獨立開來,算法的增減,修改都不會影響到環(huán)境和客戶端。
                 
                  22、TEMPLATE METHOD——看過《如何說服女生上床》這部經典文章嗎?女生從認識到上床的不變的步驟分為巧遇、打破僵局、展開追求、接吻、前戲、動手、愛撫、進去八大步驟(Template method),但每個步驟針對不同的情況,都有不一樣的做法,這就要看你隨機應變啦(具體實現(xiàn));
                 
                  模板方法模式:模板方法模式準備一個抽象類,將部分邏輯以具體方法以及具體構造子的形式實現(xiàn),然后聲明一些抽象方法來迫使子類實現(xiàn)剩余的邏輯。不同的子類可以以不同的方式實現(xiàn)這些抽象方法,從而對剩余的邏輯有不同的實現(xiàn)。先制定一個頂級邏輯框架,而將邏輯的細節(jié)留給具體的子類去實現(xiàn)。
                 
                  23、VISITOR—情人節(jié)到了,要給每個MM送一束鮮花和一張卡片,可是每個MM送的花都要針對她個人的特點,每張卡片也要根據(jù)個人的特點來挑,我一個人哪搞得清楚,還是找花店老板和禮品店老板做一下Visitor,讓花店老板根據(jù)MM的特點選一束花,讓禮品店老板也根據(jù)每個人特點選一張卡,這樣就輕松多了;
                 
                  訪問者模式:訪問者模式的目的是封裝一些施加于某種數(shù)據(jù)結構元素之上的操作。一旦這些操作需要修改的話,接受這個操作的數(shù)據(jù)結構可以保持不變。訪問者模式適用于數(shù)據(jù)結構相對未定的系統(tǒng),它把數(shù)據(jù)結構和作用于結構上的操作之間的耦合解脫開,使得操作集合可以相對自由的演化。訪問者模式使得增加新的操作變的很容易,就是增加一個新的訪問者類。訪問者模式將有關的行為集中到一個訪問者對象中,而不是分散到一個個的節(jié)點類中。當使用訪問者模式時,要將盡可能多的對象瀏覽邏輯放在訪問者類中,而不是放到它的子類中。訪問者模式可以跨過幾個類的等級結構訪問屬于不同的等級結構的成員類。
          posted @ 2005-09-20 20:06 小海船 閱讀(308) | 評論 (0)編輯 收藏

          作者:郎云鵬(dev2dev ID: hippiewolf)

          摘要:雖然session機制在web應用程序中被采用已經很長時間了,但是仍然有很多人不清楚session機制的本質,以至不能正確的應用這一技術。本文將詳細討論session的工作機制并且對在Java web application中應用session機制時常見的問題作出解答。

          目錄:
          一、術語session
          二、HTTP協(xié)議與狀態(tài)保持
          三、理解cookie機制
          四、理解session機制
          五、理解javax.servlet.http.HttpSession
          六、HttpSession常見問題
          七、跨應用程序的session共享
          八、總結
          參考文檔

          一、術語session
          在我的經驗里,session這個詞被濫用的程度大概僅次于transaction,更加有趣的是transaction與session在某些語境下的含義是相同的。

          session,中文經常翻譯為會話,其本來的含義是指有始有終的一系列動作/消息,比如打電話時從拿起電話撥號到掛斷電話這中間的一系列過程可以稱之為一個 session。有時候我們可以看到這樣的話“在一個瀏覽器會話期間,...”,這里的會話一詞用的就是其本義,是指從一個瀏覽器窗口打開到關閉這個期間 ①。最混亂的是“用戶(客戶端)在一次會話期間”這樣一句話,它可能指用戶的一系列動作(一般情況下是同某個具體目的相關的一系列動作,比如從登錄到選購商品到結賬登出這樣一個網上購物的過程,有時候也被稱為一個transaction),然而有時候也可能僅僅是指一次連接,也有可能是指含義①,其中的差別只能靠上下文來推斷②。

          然而當session一詞與網絡協(xié)議相關聯(lián)時,它又往往隱含了“面向連接”和/或“保持狀態(tài)”這樣兩個含義, “面向連接”指的是在通信雙方在通信之前要先建立一個通信的渠道,比如打電話,直到對方接了電話通信才能開始,與此相對的是寫信,在你把信發(fā)出去的時候你并不能確認對方的地址是否正確,通信渠道不一定能建立,但對發(fā)信人來說,通信已經開始了。“保持狀態(tài)”則是指通信的一方能夠把一系列的消息關聯(lián)起來,使得消息之間可以互相依賴,比如一個服務員能夠認出再次光臨的老顧客并且記得上次這個顧客還欠店里一塊錢。這一類的例子有“一個TCP session”或者 “一個POP3 session”③。

          而到了web服務器蓬勃發(fā)展的時代,session在web開發(fā)語境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務器之間保持狀態(tài)的解決方案④。有時候session也用來指這種解決方案的存儲結構,如“把xxx保存在session 里”⑤。由于各種用于web開發(fā)的語言在一定程度上都提供了對這種解決方案的支持,所以在某種特定語言的語境下,session也被用來指代該語言的解決方案,比如經常把Java里提供的javax.servlet.http.HttpSession簡稱為session⑥。

          鑒于這種混亂已不可改變,本文中session一詞的運用也會根據(jù)上下文有不同的含義,請大家注意分辨。
          在本文中,使用中文“瀏覽器會話期間”來表達含義①,使用“session機制”來表達含義④,使用“session”表達含義⑤,使用具體的“HttpSession”來表達含義⑥

          二、HTTP協(xié)議與狀態(tài)保持
          HTTP 協(xié)議本身是無狀態(tài)的,這與HTTP協(xié)議本來的目的是相符的,客戶端只需要簡單的向服務器請求下載某些文件,無論是客戶端還是服務器都沒有必要紀錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關系一樣。

          然而聰明(或者貪心?)的人們很快發(fā)現(xiàn)如果能夠提供一些按需生成的動態(tài)信息會使web變得更加有用,就像給有線電視加上點播功能一樣。這種需求一方面迫使HTML逐步添加了表單、腳本、DOM等客戶端行為,另一方面在服務器端則出現(xiàn)了CGI規(guī)范以響應客戶端的動態(tài)請求,作為傳輸載體的HTTP協(xié)議也添加了文件上載、 cookie這些特性。其中cookie的作用就是為了解決HTTP協(xié)議無狀態(tài)的缺陷所作出的努力。至于后來出現(xiàn)的session機制則是又一種在客戶端與服務器之間保持狀態(tài)的解決方案。

          讓我們用幾個例子來描述一下cookie和session機制之間的區(qū)別與聯(lián)系。筆者曾經常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優(yōu)惠,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀錄某位顧客的消費數(shù)量。想象一下其實也無外乎下面的幾種方案:
          1、該店的店員很厲害,能記住每位顧客的消費數(shù)量,只要顧客一走進咖啡店,店員就知道該怎么對待了。這種做法就是協(xié)議本身支持狀態(tài)。
          2、發(fā)給顧客一張卡片,上面記錄著消費的數(shù)量,一般還有個有效期限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以后的消費相聯(lián)系起來。這種做法就是在客戶端保持狀態(tài)。
          3、發(fā)給顧客一張會員卡,除了卡號之外什么信息也不紀錄,每次消費時,如果顧客出示該卡片,則店員在店里的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種做法就是在服務器端保持狀態(tài)。

          由于HTTP協(xié)議是無狀態(tài)的,而出于種種考慮也不希望使之成為有狀態(tài)的,因此,后面兩種方案就成為現(xiàn)實的選擇。具體來說cookie機制采用的是在客戶端保持狀態(tài)的方案,而session機制采用的是在服務器端保持狀態(tài)的方案。同時我們也看到,由于采用服務器端保持狀態(tài)的方案在客戶端也需要保存一個標識,所以session機制可能需要借助于cookie機制來達到保存標識的目的,但實際上它還有其他選擇。

          三、理解cookie機制 
          cookie機制的基本原理就如上面的例子一樣簡單,但是還有幾個問題需要解決:“會員卡”如何分發(fā);“會員卡”的內容;以及客戶如何使用“會員卡”。

          正統(tǒng)的cookie分發(fā)是通過擴展HTTP協(xié)議來實現(xiàn)的,服務器通過在HTTP的響應頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。

          而cookie 的使用是由瀏覽器按照一定的原則在后臺自動發(fā)送給服務器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用范圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發(fā)送給服務器。意思是麥當勞的會員卡只能在麥當勞的店里出示,如果某家分店還發(fā)行了自己的會員卡,那么進這家店的時候除了要出示麥當勞的會員卡,還要出示這家店的會員卡。

          cookie的內容主要包括:名字,值,過期時間,路徑和域。
          其中域可以指定某一個域比如.google.com,相當于總店招牌,比如寶潔公司,也可以指定一個域下的具體某臺機器比如www.google.com或者froogle.google.com,可以用飄柔來做比。
          路徑就是跟在域名后面的URL路徑,比如/或者/foo等等,可以用某飄柔專柜做比。
          路徑與域合在一起就構成了cookie的作用范圍。
          如果不設置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,只要關閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽器會話期的 cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內存里,當然這種行為并不是規(guī)范規(guī)定的。如果設置了過期時間,瀏覽器就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie仍然有效直到超過設定的過期時間。

          存儲在硬盤上的cookie 可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內存里的cookie,不同的瀏覽器有不同的處理方式。對于IE,在一個打開的窗口上按 Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享,而使用其他方式新開的IE進程則不能共享已經打開的窗口的內存cookie;對于 Mozilla Firefox0.8,所有的進程和標簽頁都可以共享同樣的cookie。一般來說是用javascript的window.open打開的窗口會與原窗口共享內存cookie。瀏覽器對于會話cookie的這種只認cookie不認人的處理方式經常給采用session機制的web應用程序開發(fā)者造成很大的困擾。

          下面就是一個goolge設置cookie的響應頭的例子
          HTTP/1.1 302 Found
          Location: http://www.google.com/intl/zh-CN/
          Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
          Content-Type: text/html




          這是使用HTTPLook這個HTTP Sniffer軟件來俘獲的HTTP通訊紀錄的一部分




          瀏覽器在再次訪問goolge的資源時自動向外發(fā)送cookie

           


          使用Firefox可以很容易的觀察現(xiàn)有的cookie的值
          使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。




          IE也可以設置在接受cookie前詢問

           


          這是一個詢問接受cookie的對話框。

          四、理解session機制
          session機制是一種服務器端的機制,服務器使用一種類似于散列表的結構(也可能就是使用散列表)來保存信息。

          當程序需要為某個客戶端的請求創(chuàng)建一個session的時候,服務器首先檢查這個客戶端的請求里是否已包含了一個session標識 - 稱為 session id,如果已包含一個session id則說明以前已經為此客戶端創(chuàng)建過session,服務器就按照session id把這個 session檢索出來使用(如果檢索不到,可能會新建一個),如果客戶端請求不包含session id,則為此客戶端創(chuàng)建一個session并且生成一個與此session相關聯(lián)的session id,session id的值應該是一個既不會重復,又不容易被找到規(guī)律以仿造的字符串,這個 session id將被在本次響應中返回給客戶端保存。

          保存這個session id的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動的按照規(guī)則把這個標識發(fā)揮給服務器。一般這個cookie的名字都是類似于SEEESIONID,而。比如weblogic對于web應用程序生成的cookie,JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是 JSESSIONID。

          由于cookie可以被人為的禁止,必須有其他機制以便在cookie被禁止時仍然能夠把session id傳遞回服務器。經常被使用的一種技術叫做URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種,一種是作為URL路徑的附加信息,表現(xiàn)形式為http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
          另一種是作為查詢字符串附加在URL后面,表現(xiàn)形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
          這兩種方式對于用戶來說是沒有區(qū)別的,只是服務器在解析的時候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數(shù)區(qū)分開來。
          為了在整個交互過程中始終保持狀態(tài),就必須在每個客戶端可能請求的路徑后面都包含這個session id。

          另一種技術叫做表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時能夠把session id傳遞回服務器。比如下面的表單
          <form name="testform" action="/xxx">
          <input type="text">
          </form>
          在被傳遞給客戶端之前將被改寫成
          <form name="testform" action="/xxx">
          <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
          <input type="text">
          </form>
          這種技術現(xiàn)在已較少應用,筆者接觸過的很古老的iPlanet6(SunONE應用服務器的前身)就使用了這種技術。
          實際上這種技術可以簡單的用對action應用URL重寫來代替。

          在談論session機制的時候,常常聽到這樣一種誤解“只要關閉瀏覽器,session就消失了”。其實可以想象一下會員卡的例子,除非顧客主動對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務器刪除一個session,否則服務器會一直保留,程序一般都是在用戶做log off的時候發(fā)個指令去刪除session。然而瀏覽器從來不會主動在關閉之前通知服務器它將要關閉,因此服務器根本不會有機會知道瀏覽器已經關閉,之所以會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關閉瀏覽器后這個 session id就消失了,再次連接服務器時也就無法找到原來的session。如果服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發(fā)出的HTTP請求頭,把原來的session id發(fā)送給服務器,則再次打開瀏覽器仍然能夠找到原來的session。

          恰恰是由于關閉瀏覽器不會導致session被刪除,迫使服務器為seesion設置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務器就可以認為客戶端已經停止了活動,才會把session刪除以節(jié)省存儲空間。

          五、理解javax.servlet.http.HttpSession
          HttpSession是Java平臺對session機制的實現(xiàn)規(guī)范,因為它僅僅是個接口,具體到每個web應用服務器的提供商,除了對規(guī)范支持之外,仍然會有一些規(guī)范里沒有規(guī)定的細微差異。這里我們以BEA的Weblogic Server8.1作為例子來演示。

          首先,Weblogic Server提供了一系列的參數(shù)來控制它的HttpSession的實現(xiàn),包括使用cookie的開關選項,使用URL重寫的開關選項,session持久化的設置,session失效時間的設置,以及針對cookie的各種設置,比如設置cookie的名字、路徑、域, cookie的生存時間等。

          一般情況下,session都是存儲在內存里,當服務器進程被停止或者重啟的時候,內存里的session也會被清空,如果設置了session的持久化特性,服務器就會把session保存到硬盤上,當服務器進程重新啟動或這些信息將能夠被再次使用, Weblogic Server支持的持久性方式包括文件、數(shù)據(jù)庫、客戶端cookie保存和復制。

          復制嚴格說來不算持久化保存,因為session實際上還是保存在內存里,不過同樣的信息被復制到各個cluster內的服務器進程中,這樣即使某個服務器進程停止工作也仍然可以從其他進程中取得session。

          cookie生存時間的設置則會影響瀏覽器生成的cookie是否是一個會話cookie。默認是使用會話cookie。有興趣的可以用它來試驗我們在第四節(jié)里提到的那個誤解。

          cookie的路徑對于web應用程序來說是一個非常重要的選項,Weblogic Server對這個選項的默認處理方式使得它與其他服務器有明顯的區(qū)別。后面我們會專題討論。

          關于session的設置參考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

          六、HttpSession常見問題
          (在本小節(jié)中session的含義為⑤和⑥的混合)


          1、session在何時被創(chuàng)建
          一個常見的誤解是以為session在有客戶端訪問時就被創(chuàng)建,然而事實是直到某server端程序調用 HttpServletRequest.getSession(true)這樣的語句時才被創(chuàng)建,注意如果JSP沒有顯示的使用 <% @page session="false"%> 關閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句 HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的 session對象的來歷。

          由于session會消耗內存資源,因此,如果不打算使用session,應該在所有的JSP中關閉它。

          2、session何時被刪除
          綜合前面的討論,session在下列情況下被刪除a.程序調用HttpSession.invalidate();或b.距離上一次收到客戶端發(fā)送的session id時間間隔超過了session的超時設置;或c.服務器進程被停止(非持久session)

          3、如何做到在瀏覽器關閉時刪除session
          嚴格的講,做不到這一點。可以做一點努力的辦法是在所有的客戶端頁面里使用javascript代碼window.oncolose來監(jiān)視瀏覽器的關閉動作,然后向服務器發(fā)送一個請求來刪除session。但是對于瀏覽器崩潰或者強行殺死進程這些非常規(guī)手段仍然無能為力。

          4、有個HttpSessionListener是怎么回事
          你可以創(chuàng)建這樣的listener去監(jiān)控session的創(chuàng)建和銷毀事件,使得在發(fā)生這樣的事件時你可以做一些相應的工作。注意是session的創(chuàng)建和銷毀動作觸發(fā)listener,而不是相反。類似的與HttpSession有關的listener還有 HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。

          5、存放在session中的對象必須是可序列化的嗎
          不是必需的。要求對象可序列化只是為了session能夠在集群中被復制或者能夠持久保存或者在必要時server能夠暫時把session交換出內存。在 Weblogic Server的session中放置一個不可序列化的對象在控制臺上會收到一個警告。我所用過的某個iPlanet版本如果 session中有不可序列化的對象,在session銷毀時會有一個Exception,很奇怪。

          6、如何才能正確的應付客戶端禁止cookie的可能性
          對所有的URL使用URL重寫,包括超鏈接,form的action,和重定向的URL,具體做法參見[6]
          http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

          7、開兩個瀏覽器窗口訪問應用程序會使用同一個session還是不同的session
          參見第三小節(jié)對cookie的討論,對session來說是只認id不認人,因此不同的瀏覽器,不同的窗口打開方式以及不同的cookie存儲方式都會對這個問題的答案有影響。

          8、如何防止用戶打開兩個瀏覽器窗口操作導致的session混亂
          這個問題與防止表單多次提交是類似的,可以通過設置客戶端的令牌來解決。就是在服務器每次生成一個不同的id返回給客戶端,同時保存在session里,客戶端提交表單時必須把這個id也返回服務器,程序首先比較返回的id與保存在session里的值是否一致,如果不一致則說明本次操作已經被提交過了。可以參看《J2EE核心模式》關于表示層模式的部分。需要注意的是對于使用javascript window.open打開的窗口,一般不設置這個id,或者使用單獨的id,以防主窗口無法操作,建議不要再window.open打開的窗口里做修改操作,這樣就可以不用設置。

          9、為什么在Weblogic Server中改變session的值后要重新調用一次session.setValue
          做這個動作主要是為了在集群環(huán)境中提示Weblogic Server session中的值發(fā)生了改變,需要向其他服務器進程復制新的session值。

          10、為什么session不見了
          排除session正常失效的因素之外,服務器本身的可能性應該是微乎其微的,雖然筆者在iPlanet6SP1加若干補丁的Solaris版本上倒也遇到過;瀏覽器插件的可能性次之,筆者也遇到過3721插件造成的問題;理論上防火墻或者代理服務器在cookie處理上也有可能會出現(xiàn)問題。
          出現(xiàn)這一問題的大部分原因都是程序的錯誤,最常見的就是在一個應用程序中去訪問另外一個應用程序。我們在下一節(jié)討論這個問題。

          七、跨應用程序的session共享

          常常有這樣的情況,一個大項目被分割成若干小項目開發(fā),為了能夠互不干擾,要求每個小項目作為一個單獨的web應用程序開發(fā),可是到了最后突然發(fā)現(xiàn)某幾個小項目之間需要共享一些信息,或者想使用session來實現(xiàn)SSO(single sign on),在session中保存login的用戶信息,最自然的要求是應用程序間能夠訪問彼此的session。

          然而按照Servlet規(guī)范,session的作用范圍應該僅僅限于當前應用程序下,不同的應用程序之間是不能夠互相訪問對方的session的。各個應用服務器從實際效果上都遵守了這一規(guī)范,但是實現(xiàn)的細節(jié)卻可能各有不同,因此解決跨應用程序session共享的方法也各不相同。

          首先來看一下Tomcat是如何實現(xiàn)web應用程序之間session的隔離的,從 Tomcat設置的cookie路徑來看,它對不同的應用程序設置的cookie路徑是不同的,這樣不同的應用程序所用的session id是不同的,因此即使在同一個瀏覽器窗口里訪問不同的應用程序,發(fā)送給服務器的session id也可以是不同的。


            

          根據(jù)這個特性,我們可以推測Tomcat中session的內存結構大致如下。


           

          筆者以前用過的iPlanet也采用的是同樣的方式,估計SunONE與iPlanet之間不會有太大的差別。對于這種方式的服務器,解決的思路很簡單,實際實行起來也不難。要么讓所有的應用程序共享一個session id,要么讓應用程序能夠獲得其他應用程序的session id。

          iPlanet中有一種很簡單的方法來實現(xiàn)共享一個session id,那就是把各個應用程序的cookie路徑都設為/(實際上應該是/NASApp,對于應用程序來講它的作用相當于根)。
          <session-info>
          <path>/NASApp</path>
          </session-info>

          需要注意的是,操作共享的session應該遵循一些編程約定,比如在session attribute名字的前面加上應用程序的前綴,使得 setAttribute("name", "neo")變成setAttribute("app1.name", "neo"),以防止命名空間沖突,導致互相覆蓋。


          在Tomcat中則沒有這么方便的選擇。在Tomcat版本3上,我們還可以有一些手段來共享session。對于版本4以上的Tomcat,目前筆者尚未發(fā)現(xiàn)簡單的辦法。只能借助于第三方的力量,比如使用文件、數(shù)據(jù)庫、JMS或者客戶端cookie,URL參數(shù)或者隱藏字段等手段。

          我們再看一下Weblogic Server是如何處理session的。


            

          從截屏畫面上可以看到Weblogic Server對所有的應用程序設置的cookie的路徑都是/,這是不是意味著在Weblogic Server中默認的就可以共享session了呢?然而一個小實驗即可證明即使不同的應用程序使用的是同一個session,各個應用程序仍然只能訪問自己所設置的那些屬性。這說明Weblogic Server中的session的內存結構可能如下


           

          對于這樣一種結構,在 session機制本身上來解決session共享的問題應該是不可能的了。除了借助于第三方的力量,比如使用文件、數(shù)據(jù)庫、JMS或者客戶端 cookie,URL參數(shù)或者隱藏字段等手段,還有一種較為方便的做法,就是把一個應用程序的session放到ServletContext中,這樣另外一個應用程序就可以從ServletContext中取得前一個應用程序的引用。示例代碼如下,

          應用程序A
          context.setAttribute("appA", session); 

          應用程序B
          contextA = context.getContext("/appA");
          HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); 

          值得注意的是這種用法不可移植,因為根據(jù)ServletContext的JavaDoc,應用服務器可以處于安全的原因對于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通過。

          那么Weblogic Server為什么要把所有的應用程序的cookie路徑都設為/呢?原來是為了SSO,凡是共享這個session的應用程序都可以共享認證的信息。一個簡單的實驗就可以證明這一點,修改首先登錄的那個應用程序的描述符weblogic.xml,把cookie路徑修改為/appA 訪問另外一個應用程序會重新要求登錄,即使是反過來,先訪問cookie路徑為/的應用程序,再訪問修改過路徑的這個,雖然不再提示登錄,但是登錄的用戶信息也會丟失。注意做這個實驗時認證方式應該使用FORM,因為瀏覽器和web服務器對basic認證方式有其他的處理方式,第二次請求的認證不是通過 session來實現(xiàn)的。具體請參看[7] secion 14.8 Authorization,你可以修改所附的示例程序來做這些試驗。

          八、總結
          session機制本身并不復雜,然而其實現(xiàn)和配置上的靈活性卻使得具體情況復雜多變。這也要求我們不能把僅僅某一次的經驗或者某一個瀏覽器,服務器的經驗當作普遍適用的經驗,而是始終需要具體情況具體分析。

          關于作者:
          郎云鵬(dev2dev ID: hippiewolf),軟件工程師,從事J2EE開發(fā)
          電子郵件:langyunpeng@yahoo.com.cn
          地址:大連軟件園路31號科技大廈A座大連博涵咨詢服務有限公司

          參考文檔:
          [1] Preliminary Specification http://wp.netscape.com/newsref/std/cookie_spec.html
          [2] RFC2109 http://www.rfc-editor.org/rfc/rfc2109.txt
          [3] RFC2965 http://www.rfc-editor.org/rfc/rfc2965.txt
          [4] The Unofficial Cookie FAQ http://www.cookiecentral.com/faq/
          [5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
          [6] http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
          [7] RFC2616 http://www.rfc-editor.org/rfc/rfc2616.txt
          posted @ 2005-09-20 10:04 小海船 閱讀(1472) | 評論 (4)編輯 收藏

          最近在項目當中負責了登錄模塊的開發(fā),采取的方式是最常見的那種,即提交表單到業(yè)務層對數(shù)據(jù)庫中的字段進行比較。這使我有了進一步想了解JAAS的沖動,sun的tutorial還沒來得及看,E文看的比較慢,還是先google出中文得到一個大概的印象吧。
          Java Authentication and Authorization Service 在 JDK 1.4 正式加入 Java 標準的 Security 模塊, 簡單來說, JAAS 就是讓我們把身份驗證與權限管控使用標準的方法來開發(fā)。根據(jù)google到的信息得知。
          JAAS主要的類如下:
          普通對象
          • Subject
          • Principals
          • Credentials
          身份驗證相關的對象
          • LoginContext
          • LoginModule
          • CallbackHandler
          • Callback
          權限管控相關的對象
          • Policy
          • AuthPermission
          • PrivateCredentialPermission

          接下來便是利用空余時間通過JDK,tutorial進行研究了。希望不是半途而廢,也謹以此文記下,以表決心。

          (注明:這是一個網友的文章,記此是希望和他一起學習,共勉 )
          posted @ 2005-09-19 15:40 小海船 閱讀(264) | 評論 (0)編輯 收藏

          使用Java操作二進制文件使用Java操作文本文件兩篇文章分別介紹了如何操作二進制文件和文本文件,事實上Java中還有基于Data的數(shù)據(jù)操作,這里的Data指的是Java的基本數(shù)據(jù)類型和String。基本數(shù)據(jù)類型包括byte、int、char、long、float、double、boolean和short。

              說到Java的基本數(shù)據(jù)類型必須談到的兩個類是DataInputStream和DataOutputStream。它們提供了對Java基本數(shù)據(jù)類型的操作,但是這些方法事實上是在兩個重要的接口中定義的DataInput和DataOutput,它們的功能就是把二進制的字節(jié)流轉換成Java的基本數(shù)據(jù)類型,同時還提供了從數(shù)據(jù)中使用UTF-8編碼構建String的功能。有一個重要的類RandomAccessFile實現(xiàn)了DataInput和DataOutput兩個接口使得他能夠對文件同時進行寫和讀的操作。

              在DataInputStream和DataOutputStream兩個類中的方法都很簡單,基本結構為readXXXX()和writeXXXX()其中XXXX代表基本數(shù)據(jù)類型或者String。在這里不多講述,不過值得一提的是我們有必要讀讀java中unicode的編碼規(guī)則,在API doc中有比較詳細的介紹。通常我們的對象有很多都是由java的基本數(shù)據(jù)類型構成的,比如一個人的信息包括姓名,電子信箱,電話號碼和性別等。其實我們可以用DataInputStream中的方法和DataOutputStream中的方法按照一定的序列把數(shù)據(jù)寫入流中再按照相同的序列把他們讀取出來,這就是我們自己實現(xiàn)的序列化,這可以用在數(shù)據(jù)傳輸中,比如在J2ME聯(lián)網程序中使用序列化機制傳輸數(shù)據(jù)。下面我們看看如何自己實現(xiàn)序列化,首先我們要有兩個構造函數(shù)其中一個參數(shù)為空。
          public Account()
              {

              }

              public Account(String userName, String email, int age, boolean gender)
              {
                  this.userName = userName;
                  this.email = email;
                  this.age = age;
                  this.gender = gender;
              }
          當我們進行序列化的時候也很簡單,我們只是往DataOutputStream中按照順序寫入對象的成員變量。例如
          public void serialize(DataOutputStream dos) throws IOException
              {
                  dos.writeUTF(userName);
                  dos.writeUTF(email);
                  dos.writeInt(age);
                  dos.writeBoolean(gender);
                 
              }
          當我們進行反序列化的時候則按照相同的順序從DataInputStream里面讀取數(shù)據(jù)并賦值給成員變量。例如
              public static Account deserialize(DataInputStream dis) throws IOException
              {
                  Account account = new Account();
                  account.userName = dis.readUTF();
                  account.email = dis.readUTF();
                  account.age = dis.readInt();
                  account.gender = dis.readBoolean();
                
                  return account;
              }
          為了便于調試我們還提供一個toString()的方法打印出對象的實際信息。這是個好的習慣。
              public String toString()
              {
                  return "UserName = " + userName + " Email = " + email + " age = " + age
                          + " gender = " + (gender ? "male" : "female");
              }

               為了測試序列化我們編寫下面的程序進行測試,代碼比較簡單。

          package com.j2medev.mingjava;

          import java.io.*;


          public class TestDataIO
          {

              public static void main(String[] args) throws IOException
              {
                  Account account = new Account("mingjava","eric.zhan@263.net",25,true);
                  System.out.println("before serialization.........");
                  System.out.println(account.toString());
                  ByteArrayOutputStream baos = new ByteArrayOutputStream();
                  DataOutputStream dos = new DataOutputStream(baos);
                  account.serialize(dos);
                  DataInputStream dis = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
                  Account sAccount = Account.deserialize(dis);
                  System.out.println("after serialization..........");
                  System.out.println(sAccount.toString());
                  dos.close();
                  dis.close();
              }
          }

          package com.j2medev.mingjava;

          import java.io.*;

          public class Account
          {
              private String userName = "";
              private String email = "";
              private int age = 0;
              private boolean gender = false;

              public Account()
              {

              }

              public Account(String userName, String email, int age, boolean gender)
              {
                  this.userName = userName;
                  this.email = email;
                  this.age = age;
                  this.gender = gender;
              }

              public void serialize(DataOutputStream dos) throws IOException
              {
                  dos.writeUTF(userName);
                  dos.writeUTF(email);
                  dos.writeInt(age);
                  dos.writeBoolean(gender);
                 
              }

              public static Account deserialize(DataInputStream dis) throws IOException
              {
                  Account account = new Account();
                  account.userName = dis.readUTF();
                  account.email = dis.readUTF();
                  account.age = dis.readInt();
                  account.gender = dis.readBoolean();
                
                  return account;
              }

              public String toString()
              {
                  return "UserName = " + userName + " Email = " + email + " age = " + age
                          + " gender = " + (gender ? "male" : "female");
              }
          }
          編譯運行程序在控制臺輸出:
          before serialization.........
          UserName = mingjava Email = eric.zhan@263.net age = 25 gender = male
          after serialization..........
          UserName = mingjava Email = eric.zhan@263.net age = 25 gender = male
          序列化成功,后面我將講述如何在J2ME聯(lián)網中使用序列化機制。

          posted @ 2005-09-19 09:26 小海船 閱讀(292) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 灵武市| 修武县| 万全县| 筠连县| 莆田市| 陵水| 宿松县| 富川| 泰和县| 江永县| 开江县| 陆良县| 香港| 车致| 禹城市| 鸡西市| 莱阳市| 绩溪县| 虞城县| 嫩江县| 汾西县| 城市| 中宁县| 龙门县| 新河县| 梁平县| 缙云县| 介休市| 浦北县| 西城区| 井冈山市| 延安市| 望奎县| 阳谷县| 潼南县| 黄平县| 前郭尔| 江阴市| 修武县| 明溪县| 当涂县|