licheng700

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

          2005年9月28日 #

           

          jungleford如是說(shuō)

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

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

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

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

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

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

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

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

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

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

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

          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()里調(diào)用默認(rèn)的序列化方法ObjectOutputStream.defaultWriteObject();譬如你不愿意將某些敏感的屬性和信息序列化,你也可以調(diào)用ObjectOutputStream.writeObject()方法明確指定需要序列化那些屬性。關(guān)于用戶可定制的序列化方法,我們將在后面提到。

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

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

              如果一個(gè)bean是如下格式:

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

          那么通過(guò)XMLEcoder序列化出來(lái)的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,當(dāng)然也是可以用這種方式序列化的,下面就是從JDK文檔中摘錄的一個(gè)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ù)是一些不是太復(fù)雜的類型的話,把它做成bean再序列化也不失為一種方便的選擇。

          Properties
              在以前我總結(jié)的一篇關(guān)于集合框架的小文章里提到過(guò),Properties是歷史集合類的一個(gè)典型的例子,這里主要不是介紹它的集合特性。大家可能都經(jīng)常接觸一些配置文件,如Windows的ini文件,Apache的conf文件,還有Java里的properties文件等,這些文件當(dāng)中的數(shù)據(jù)以“關(guān)鍵字-值”對(duì)的方式保存。“環(huán)境變量”這個(gè)概念都知道吧,它也是一種“key-value”對(duì),以前也常常看到版上問(wèn)“如何取得系統(tǒng)某某信息”之類的問(wèn)題,其實(shí)很多都保存在環(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\項(xiàng)目\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\項(xiàng)目\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() 從一個(gè)外部流讀取屬性
          store() 將屬性保存到外部流(特別是文件)
          getProperty() 取得一個(gè)指定的屬性
          setProperty() 設(shè)置一個(gè)指定的屬性
          list() 列出這個(gè)Properties對(duì)象包含的全部“key-value”對(duì)
          System.getProperties() 取得系統(tǒng)當(dāng)前的環(huán)境變量

              你可以這樣保存一個(gè)properties文件:

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

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

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

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

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

          網(wǎng)絡(luò)I/O:Socket→RMI

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

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

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

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

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

          CMP和Hibernate
              因?yàn)槲覍?duì)J2EE的東西不是太熟悉,隨便找了點(diǎn)材料看看,所以擔(dān)心說(shuō)的不到位,這次就不作具體總結(jié)了,人要學(xué)習(xí)……真是一件痛苦的事情 

          序列化再探討

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

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

          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;
          ...

              注意,這里我們?cè)趯?shí)例化a和b的時(shí)候,有意讓他們的c屬性使用同一個(gè)C類型對(duì)象的引用,譬如c1,那么請(qǐng)?jiān)囅胍幌拢覀冃蛄谢痑和b的時(shí)候,它們的c屬性在外部字節(jié)流(當(dāng)然可以不僅僅是文件)里保存的是一份拷貝還是兩份拷貝呢?序列化在這里使用的是一種類似于“指針”的方案:它為每個(gè)被序列化的對(duì)象標(biāo)上一個(gè)“序列號(hào)”(serial number),但序列化一個(gè)對(duì)象的時(shí)候,如果其某個(gè)屬性對(duì)象是已經(jīng)被序列化的,那么這里只向輸出流寫入該屬性的序列號(hào);從字節(jié)流恢復(fù)被序列化的對(duì)象時(shí),也根據(jù)序列號(hào)找到對(duì)應(yīng)的流來(lái)恢復(fù)。這就是“序列化”名稱的由來(lái)!這里我們看到“序列化”和“指針”是極相似的,只不過(guò)“指針”是內(nèi)存空間的地址鏈,而序列化用的是外部流中的“序列號(hào)鏈”
              使用“序列號(hào)”而不是內(nèi)存地址來(lái)標(biāo)識(shí)一個(gè)被序列化的對(duì)象,是因?yàn)閺牧髦谢謴?fù)對(duì)象到內(nèi)存,其地址可能就未必是原來(lái)的地址了——我們需要的只是這些對(duì)象之間的引用關(guān)系,而不是死板的原始位置,這在RMI中就更是必要,在兩臺(tái)不同的機(jī)器之間傳遞對(duì)象(流),根本就不可能指望它們?cè)趦膳_(tái)機(jī)器上都具有相同的內(nèi)存地址。

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

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

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

          序列化與克隆
              從“可序列化”的遞歸定義來(lái)看,一個(gè)序列化的對(duì)象貌似對(duì)象內(nèi)存映象的外部克隆,如果沒(méi)有共享引用的屬性的化,那么應(yīng)該是一個(gè)深度克隆。關(guān)于克隆的話題有可以談很多,這里就不細(xì)說(shuō)了,有興趣的話可以參考IBM developerWorks上的一篇文章:JAVA中的指針,引用及對(duì)象的clone

          一點(diǎn)啟示

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

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

          2005年9月27日 #

          1。追加文件方法
                    1.java.io.FileWriter 的構(gòu)造函數(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)將當(dāng)前操作指針移到文件末尾。
          2.關(guān)于隔行寫入文件java.io.BufferedWriter類中的方法newLine()



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

          2005年9月26日 #

          Log4j 學(xué)習(xí)筆記

          ccjsmile (http://ijsp.net)

           

          這是我在學(xué)習(xí)Log4j時(shí)做的一點(diǎn)筆記,希望對(duì)各位朋友有一點(diǎn)幫助。我的mail:ccjsmile@sohu.com,希望能與您進(jìn)行討論^_*

           

          Log4j 是一個(gè)開(kāi)放源碼項(xiàng)目,它是一個(gè)日志管理程序。

          Log4j的優(yōu)點(diǎn):

          1.       方便的調(diào)試信息;

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

          缺點(diǎn):減慢程序運(yùn)行速度.


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

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

           

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

          org.apache.log4j.ConsoleAppender(控制臺(tái)),

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

          org.apache.log4j.DailyRollingFileAppender(每天產(chǎn)生一個(gè)日志文件),org.apache.log4j.RollingFileAppender(文件大小到達(dá)指定尺寸的時(shí)候產(chǎn)生一個(gè)新的文件),

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

           

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

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

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

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

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

           

           

          下面介紹一下log4jweb中應(yīng)用的例子:

          這是一個(gè)用于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)需要一個(gè)log4j.properties的文件,他的存放路徑為:D:/resin/webapps/log4j/web-inf/classes/log4j.properties

          這個(gè)properties的文件內(nèi)容如下:

          #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

           

          因?yàn)檫@是一個(gè)servlet文件,同時(shí)我們還要修改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>

           

          下面這兩個(gè)為測(cè)試文件:

          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 小海船 閱讀(341) | 評(píng)論 (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) | 評(píng)論 (0)編輯 收藏

          2005年9月21日 #

          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()); // 測(cè)試調(diào)用
           }
          }
          templateId.properties文件內(nèi)容:templateId=FFD4156506-3-2F8CAC7

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

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

                 由于類裝入器擔(dān)負(fù)著把代碼裝入JVM的重任,所以它的體系結(jié)構(gòu)應(yīng)當(dāng)保證整個(gè)平臺(tái)的安全性不會(huì)受到威脅。每一個(gè)類裝入器要為它裝入的類定義一個(gè)獨(dú)立的名稱空間,因此運(yùn)行時(shí),一個(gè)類由它的包名稱和裝入它的類裝入器兩者結(jié)合唯一地標(biāo)識(shí)。

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

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

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

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

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

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

          2005年9月20日 #

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

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

          摘要:雖然session機(jī)制在web應(yīng)用程序中被采用已經(jīng)很長(zhǎng)時(shí)間了,但是仍然有很多人不清楚session機(jī)制的本質(zhì),以至不能正確的應(yīng)用這一技術(shù)。本文將詳細(xì)討論session的工作機(jī)制并且對(duì)在Java web application中應(yīng)用session機(jī)制時(shí)常見(jiàn)的問(wèn)題作出解答。

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

          一、術(shù)語(yǔ)session
          在我的經(jīng)驗(yàn)里,session這個(gè)詞被濫用的程度大概僅次于transaction,更加有趣的是transaction與session在某些語(yǔ)境下的含義是相同的。

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

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

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

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

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

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

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

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

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

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

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

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

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

          下面就是一個(gè)goolge設(shè)置cookie的響應(yīng)頭的例子
          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這個(gè)HTTP Sniffer軟件來(lái)俘獲的HTTP通訊紀(jì)錄的一部分




          瀏覽器在再次訪問(wèn)goolge的資源時(shí)自動(dòng)向外發(fā)送cookie

           


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




          IE也可以設(shè)置在接受cookie前詢問(wèn)

           


          這是一個(gè)詢問(wèn)接受cookie的對(duì)話框。

          四、理解session機(jī)制
          session機(jī)制是一種服務(wù)器端的機(jī)制,服務(wù)器使用一種類似于散列表的結(jié)構(gòu)(也可能就是使用散列表)來(lái)保存信息。

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

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

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

          另一種技術(shù)叫做表單隱藏字段。就是服務(wù)器會(huì)自動(dòng)修改表單,添加一個(gè)隱藏字段,以便在表單提交時(shí)能夠把session id傳遞回服務(wù)器。比如下面的表單
          <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>
          這種技術(shù)現(xiàn)在已較少應(yīng)用,筆者接觸過(guò)的很古老的iPlanet6(SunONE應(yīng)用服務(wù)器的前身)就使用了這種技術(shù)。
          實(shí)際上這種技術(shù)可以簡(jiǎn)單的用對(duì)action應(yīng)用URL重寫來(lái)代替。

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

          恰恰是由于關(guān)閉瀏覽器不會(huì)導(dǎo)致session被刪除,迫使服務(wù)器為seesion設(shè)置了一個(gè)失效時(shí)間,當(dāng)距離客戶端上一次使用session的時(shí)間超過(guò)這個(gè)失效時(shí)間時(shí),服務(wù)器就可以認(rèn)為客戶端已經(jīng)停止了活動(dòng),才會(huì)把session刪除以節(jié)省存儲(chǔ)空間。

          五、理解javax.servlet.http.HttpSession
          HttpSession是Java平臺(tái)對(duì)session機(jī)制的實(shí)現(xiàn)規(guī)范,因?yàn)樗鼉H僅是個(gè)接口,具體到每個(gè)web應(yīng)用服務(wù)器的提供商,除了對(duì)規(guī)范支持之外,仍然會(huì)有一些規(guī)范里沒(méi)有規(guī)定的細(xì)微差異。這里我們以BEA的Weblogic Server8.1作為例子來(lái)演示。

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

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

          復(fù)制嚴(yán)格說(shuō)來(lái)不算持久化保存,因?yàn)閟ession實(shí)際上還是保存在內(nèi)存里,不過(guò)同樣的信息被復(fù)制到各個(gè)cluster內(nèi)的服務(wù)器進(jìn)程中,這樣即使某個(gè)服務(wù)器進(jìn)程停止工作也仍然可以從其他進(jìn)程中取得session。

          cookie生存時(shí)間的設(shè)置則會(huì)影響瀏覽器生成的cookie是否是一個(gè)會(huì)話cookie。默認(rèn)是使用會(huì)話cookie。有興趣的可以用它來(lái)試驗(yàn)我們?cè)诘谒墓?jié)里提到的那個(gè)誤解。

          cookie的路徑對(duì)于web應(yīng)用程序來(lái)說(shuō)是一個(gè)非常重要的選項(xiàng),Weblogic Server對(duì)這個(gè)選項(xiàng)的默認(rèn)處理方式使得它與其他服務(wù)器有明顯的區(qū)別。后面我們會(huì)專題討論。

          關(guān)于session的設(shè)置參考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

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


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

          由于session會(huì)消耗內(nèi)存資源,因此,如果不打算使用session,應(yīng)該在所有的JSP中關(guān)閉它。

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

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

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

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

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

          7、開(kāi)兩個(gè)瀏覽器窗口訪問(wèn)應(yīng)用程序會(huì)使用同一個(gè)session還是不同的session
          參見(jiàn)第三小節(jié)對(duì)cookie的討論,對(duì)session來(lái)說(shuō)是只認(rèn)id不認(rèn)人,因此不同的瀏覽器,不同的窗口打開(kāi)方式以及不同的cookie存儲(chǔ)方式都會(huì)對(duì)這個(gè)問(wèn)題的答案有影響。

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

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

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

          七、跨應(yīng)用程序的session共享

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

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

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


            

          根據(jù)這個(gè)特性,我們可以推測(cè)Tomcat中session的內(nèi)存結(jié)構(gòu)大致如下。


           

          筆者以前用過(guò)的iPlanet也采用的是同樣的方式,估計(jì)SunONE與iPlanet之間不會(huì)有太大的差別。對(duì)于這種方式的服務(wù)器,解決的思路很簡(jiǎn)單,實(shí)際實(shí)行起來(lái)也不難。要么讓所有的應(yīng)用程序共享一個(gè)session id,要么讓應(yīng)用程序能夠獲得其他應(yīng)用程序的session id。

          iPlanet中有一種很簡(jiǎn)單的方法來(lái)實(shí)現(xiàn)共享一個(gè)session id,那就是把各個(gè)應(yīng)用程序的cookie路徑都設(shè)為/(實(shí)際上應(yīng)該是/NASApp,對(duì)于應(yīng)用程序來(lái)講它的作用相當(dāng)于根)。
          <session-info>
          <path>/NASApp</path>
          </session-info>

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


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

          我們?cè)倏匆幌耊eblogic Server是如何處理session的。


            

          從截屏畫面上可以看到Weblogic Server對(duì)所有的應(yīng)用程序設(shè)置的cookie的路徑都是/,這是不是意味著在Weblogic Server中默認(rèn)的就可以共享session了呢?然而一個(gè)小實(shí)驗(yàn)即可證明即使不同的應(yīng)用程序使用的是同一個(gè)session,各個(gè)應(yīng)用程序仍然只能訪問(wèn)自己所設(shè)置的那些屬性。這說(shuō)明Weblogic Server中的session的內(nèi)存結(jié)構(gòu)可能如下


           

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

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

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

          值得注意的是這種用法不可移植,因?yàn)楦鶕?jù)ServletContext的JavaDoc,應(yīng)用服務(wù)器可以處于安全的原因?qū)τ赾ontext.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通過(guò)。

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

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

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

          參考文檔:
          [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 小海船 閱讀(1471) | 評(píng)論 (4)編輯 收藏

          2005年9月19日 #

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

          接下來(lái)便是利用空余時(shí)間通過(guò)JDK,tutorial進(jìn)行研究了。希望不是半途而廢,也謹(jǐn)以此文記下,以表決心。

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

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

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

              在DataInputStream和DataOutputStream兩個(gè)類中的方法都很簡(jiǎn)單,基本結(jié)構(gòu)為readXXXX()和writeXXXX()其中XXXX代表基本數(shù)據(jù)類型或者String。在這里不多講述,不過(guò)值得一提的是我們有必要讀讀java中unicode的編碼規(guī)則,在API doc中有比較詳細(xì)的介紹。通常我們的對(duì)象有很多都是由java的基本數(shù)據(jù)類型構(gòu)成的,比如一個(gè)人的信息包括姓名,電子信箱,電話號(hào)碼和性別等。其實(shí)我們可以用DataInputStream中的方法和DataOutputStream中的方法按照一定的序列把數(shù)據(jù)寫入流中再按照相同的序列把他們讀取出來(lái),這就是我們自己實(shí)現(xiàn)的序列化,這可以用在數(shù)據(jù)傳輸中,比如在J2ME聯(lián)網(wǎng)程序中使用序列化機(jī)制傳輸數(shù)據(jù)。下面我們看看如何自己實(shí)現(xiàn)序列化,首先我們要有兩個(gè)構(gòu)造函數(shù)其中一個(gè)參數(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;
              }
          當(dāng)我們進(jìn)行序列化的時(shí)候也很簡(jiǎn)單,我們只是往DataOutputStream中按照順序?qū)懭雽?duì)象的成員變量。例如
          public void serialize(DataOutputStream dos) throws IOException
              {
                  dos.writeUTF(userName);
                  dos.writeUTF(email);
                  dos.writeInt(age);
                  dos.writeBoolean(gender);
                 
              }
          當(dāng)我們進(jìn)行反序列化的時(shí)候則按照相同的順序從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;
              }
          為了便于調(diào)試我們還提供一個(gè)toString()的方法打印出對(duì)象的實(shí)際信息。這是個(gè)好的習(xí)慣。
              public String toString()
              {
                  return "UserName = " + userName + " Email = " + email + " age = " + age
                          + " gender = " + (gender ? "male" : "female");
              }

               為了測(cè)試序列化我們編寫下面的程序進(jìn)行測(cè)試,代碼比較簡(jiǎn)單。

          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");
              }
          }
          編譯運(yùn)行程序在控制臺(tái)輸出:
          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)網(wǎng)中使用序列化機(jī)制。

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

          僅列出標(biāo)題  下一頁(yè)
          主站蜘蛛池模板: 阿瓦提县| 永州市| 四川省| 婺源县| 涿州市| 鹤山市| 白山市| 纳雍县| 工布江达县| 福建省| 肃南| 建湖县| 兴义市| 新丰县| 仲巴县| 湖南省| 即墨市| 海南省| 文安县| 闽侯县| 明水县| 农安县| 南汇区| 宝坻区| 焉耆| 河间市| 明水县| 云林县| 陇南市| 金堂县| 如东县| 娄烦县| 师宗县| 长丰县| 靖边县| 安达市| 方城县| 白银市| 栖霞市| 汨罗市| 晴隆县|