所謂熱部署,就是在應(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ù)設(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最初開始運(yùn)行時(shí),它里面不裝入任何類。如果要求JVM執(zhí)行一個(gè)程序,被執(zhí)行的類首先裝入,字節(jié)碼執(zhí)行期間會(huì)引用到其他類和接口,這些被引用到的類和接口隨之也被裝入。因此,有人把JVM的類裝入方式稱為“懶惰的”裝入方式,即只有必須用到某個(gè)類時(shí)才會(huì)裝入它(而不是預(yù)先裝入各種可能用到的類),正因?yàn)槿绱耍_始時(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)成的樹形結(jié)構(gòu),bootstrap類裝入器是這個(gè)樹形結(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類裝入器是處于樹形結(jié)構(gòu)的頂層,它不再有可委托的父類裝入器。
⑶ 如果父類裝入器未能裝入指定的類,則當(dāng)前的類裝入器將嘗試搜索該類。每一個(gè)類裝入器有預(yù)定義的搜索和裝入類的位置。例如,bootstrap類裝入器搜索sun.boot.class.path系統(tǒng)屬性中指定的位置(包括目錄和zip、jar文件),system類裝入器搜索的位置由JVM開始運(yùn)行時(shí)傳入的CLASSPATH命令行變量指定(相當(dāng)于設(shè)置java.class.path系統(tǒng)屬性)。如果找到了類,則類裝入器將類返回給系統(tǒng),請(qǐng)求處理完畢。
⑷ 如果找不到類,則拋出java.lang.ClassNotFoundException異常。