發(fā)信站: 一見(jiàn)如故 (Tue Apr 11 11:16:39 2006), 本站(yjrg.net)
Java類(lèi)加載內幕
作者:Binildas Christudas 01/26/2005
翻譯:purplerain
版權聲明:可以任意轉載,轉載時(shí)請務(wù)必以超鏈接形式標明文章原始出處和作者信息及本聲明
作者:
Binildas;purplerain
原文地址:
http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.html 中文地址:
http://www.matrix.org.cn/resource/article/43/43875_Class_Loading.html 關(guān)鍵詞: Java Class Loading
類(lèi)加載是java語(yǔ)言提供的最強大的機制之一。盡管類(lèi)加載并不是討論的熱點(diǎn)話(huà)題,但所有的編程人員都應該了解其工作機制,明白如何做才能讓其滿(mǎn)足我們的需要。這能有效節省我們的編碼時(shí)間,從不斷調試ClassNotFoundException, ClassCastException的工作中解脫出來(lái)。
這篇文章從基礎講起,比如代碼與數據的不同之處是什么,他們是如何構成一個(gè)實(shí)例或對象的。然后深入探討java虛擬機(
JVM)是如何利用類(lèi)加載器讀取代碼,以及java中類(lèi)加載器的主要類(lèi)型。接著(zhù)用一個(gè)類(lèi)加載的基本算法看一下類(lèi)加載器如何加載一個(gè)內部類(lèi)。本文的下一節演示一段代碼來(lái)說(shuō)明擴展和開(kāi)發(fā)屬于自己的類(lèi)加載器的必要性。緊接著(zhù)解釋如何使用定制的類(lèi)加載器來(lái)完成一個(gè)一般意義上的任務(wù),使其可以加載任意遠端客戶(hù)的代碼,在JVM中定義,實(shí)例化并執行它。本文包括了J2EE關(guān)于類(lèi)加載的規范——事實(shí)上這已經(jīng)成為了J2EE的標準之一。
類(lèi)與數據 一個(gè)類(lèi)代表要執行的代碼,而數據則表示其相關(guān)狀態(tài)。狀態(tài)時(shí)常改變,而代碼則不會(huì )。當我們將一個(gè)特定的狀態(tài)與一個(gè)類(lèi)相對應起來(lái),也就意味著(zhù)將一個(gè)類(lèi)事例化。盡管相同的類(lèi)對應的實(shí)例其狀態(tài)千差萬(wàn)別,但其本質(zhì)都對應著(zhù)同一段代碼。在JAVA中,一個(gè)類(lèi)通常有著(zhù)一個(gè).class文件,但也有例外。在JAVA的運行時(shí)環(huán)境中(Java runtime),每一個(gè)類(lèi)都有一個(gè)以第一類(lèi)(first-class)的Java對象所表現出現的代碼,其是java.lang.Class的實(shí)例。我們編譯一個(gè)JAVA文件,編譯器都會(huì )嵌入一個(gè)public, static, final修飾的類(lèi)型為java.lang.Class,名稱(chēng)為class的域變量在其字節碼文件中。因為使用了public修飾,我們可以采用如下的形式對其訪(fǎng)問(wèn):
java.lang.Class klass = Myclass.class;
一旦一個(gè)類(lèi)被載入JVM中,同一個(gè)類(lèi)就不會(huì )被再次載入了(切記,同一個(gè)類(lèi))。這里存在一個(gè)問(wèn)題就是什么是“同一個(gè)類(lèi)”?正如一個(gè)對象有一個(gè)具體的狀態(tài),即標識,一個(gè)對象始終和其代碼(類(lèi))相關(guān)聯(lián)。同理,載入JVM的類(lèi)也有一個(gè)具體的標識,我們接下來(lái)看。
在JAVA中,一個(gè)類(lèi)用其完全匹配類(lèi)名(fully qualified class name)作為標識,這里指的完全匹配類(lèi)名包括包名和類(lèi)名。但在JVM中一個(gè)類(lèi)用其全名和一個(gè)加載類(lèi)ClassLoader的實(shí)例作為唯一標識。因此,如果一個(gè)名為Pg的包中,有一個(gè)名為Cl的類(lèi),被類(lèi)加載器KlassLoader的一個(gè)實(shí)例kl1加載,Cl的實(shí)例,即C1.class在JVM中表示為(Cl, Pg, kl1)。這意味著(zhù)兩個(gè)類(lèi)加載器的實(shí)例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它們所加載的類(lèi)也因此完全不同,互不兼容的。那么在JVM中到底有多少種類(lèi)加載器的實(shí)例?下一節我們揭示答案。
類(lèi)加載器 在JVM中,每一個(gè)類(lèi)都被java.lang.ClassLoader的一些實(shí)例來(lái)加載.類(lèi)ClassLoader是在包中java.lang里,開(kāi)發(fā)者可以自由地繼承它并添加自己的功能來(lái)加載類(lèi)。
無(wú)論何時(shí)我們鍵入java MyMainClass來(lái)開(kāi)始運行一個(gè)新的JVM,“引導類(lèi)加載器(bootstrap class
loader)”負責將一些關(guān)鍵的Java類(lèi),如java.lang.Object和其他一些運行時(shí)代碼先加載進(jìn)內存中。運行時(shí)的類(lèi)在JRE\lib\rt.jar包文件中。因為這屬于系統底層執行動(dòng)作,我們無(wú)法在JAVA文檔中找到引導類(lèi)加載器的工作細節?;谕瑯拥脑?,引導類(lèi)加載器的行為在各JVM之間也是大相徑庭。
同理,如果我們按照如下方式:
log(java.lang.String.class.getClassLoader());
來(lái)獲取java的核心運行時(shí)類(lèi)的加載器,就會(huì )得到null。
接下來(lái)介紹java的擴展類(lèi)加載器。擴展庫提供比java運行代碼更多的特性,我們可以把擴展庫保存在由java.ext.dirs屬性提供的路徑中。
(編輯注:java.ext.dirs屬性指的是系統屬性下的一個(gè)key,所有的系統屬性可以通過(guò)System.getProperties()方法獲得。在編者的系統中,java.ext.dirs的value是” C:\Program Files\Java\jdk1.5.0_04\jre\lib\ext”。下面將要談到的如java.class.path也同屬系統屬性的一個(gè)key。)
類(lèi)ExtClassLoader專(zhuān)門(mén)用來(lái)加載所有java.ext.dirs下的.jar文件。開(kāi)發(fā)者可以通過(guò)把自己的.jar文件或庫文件加入到擴展目錄的classpath,使其可以被擴展類(lèi)加載器讀取。
從開(kāi)發(fā)者的角度,第三種同樣也是最重要的一種類(lèi)加載器是AppClassLoader。這種類(lèi)加載器用來(lái)讀取所有的對應在java.class.path系統屬性的路徑下的類(lèi)。
Sun的java指南中,文章“理解擴展類(lèi)加載”(Understanding
Extension Class Loading)對以上三個(gè)類(lèi)加載器路徑有更詳盡的解釋?zhuān)@是其他幾個(gè)JDK中的類(lèi)加載器
●java.net.URLClassLoader
●java.security.SecureClassLoader
●java.rmi.server.RMIClassLoader
●sun.applet.AppletClassLoader
java.lang.Thread,包含了public ClassLoader getContextClassLoader()方法,這一方法返回針對一具體線(xiàn)程的上下文環(huán)境類(lèi)加載器。此類(lèi)加載器由線(xiàn)程的創(chuàng )建者提供,以供此線(xiàn)程中運行的代碼在需要加載類(lèi)或資源時(shí)使用。如果此加載器未被建立,缺省是其父線(xiàn)程的上下文類(lèi)加載器。原始的類(lèi)加載器一般由讀取應用程序的類(lèi)加載器建立。
類(lèi)加載器如何工作? 除了引導類(lèi)加載器,所有的類(lèi)加載器都有一個(gè)父類(lèi)加載器,不僅如此,所有的類(lèi)加載器也都是java.lang.ClassLoader類(lèi)型。以上兩種類(lèi)加載器是不同的,而且對于開(kāi)發(fā)者自訂制的類(lèi)加載器的正常運行也至關(guān)重要。最重要的方面是正確設置父類(lèi)加載器。任何類(lèi)加載器,其父類(lèi)加載器是加載該類(lèi)加載器的類(lèi)加載器實(shí)例。(記住,類(lèi)加載器本身也是一個(gè)類(lèi)!)
使用loadClass()方法可以從類(lèi)加載器中獲得該類(lèi)。我們可以通過(guò)java.lang.ClassLoader的源代碼來(lái)了解該方法工作的細節,如下:
protected synchronized Class<?> loadClass
(String name, boolean resolve)
throws ClassNotFoundException{
// First check if the class is already loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke
// findClass to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
我們可以使用ClassLoader的兩種構造方法來(lái)設置父類(lèi)加載器:
public class MyClassLoader extends ClassLoader{
public MyClassLoader(){
super(MyClassLoader.class.getClassLoader());
}
}
或
public class MyClassLoader extends ClassLoader{
public MyClassLoader(){
super(getClass().getClassLoader());
}
}
第一種方式較為常用,因為通常不建議在構造方法里調用getClass()方法,因為對象的初始化只是在構造方法的出口處才完全完成。因此,如果父類(lèi)加載器被正確建立,當要示從一個(gè)類(lèi)加載器的實(shí)例獲得一個(gè)類(lèi)時(shí),如果它不能找到這個(gè)類(lèi),它應該首先去訪(fǎng)問(wèn)其父類(lèi)。如果父類(lèi)不能找到它(即其父類(lèi)也不能找不這個(gè)類(lèi),等等),而且如果findBootstrapClass0()方法也失敗了,則調用findClass()方法。findClass()方法的缺省實(shí)現會(huì )拋出ClassNotFoundException,當它們繼承java.lang.ClassLoader來(lái)訂制類(lèi)加載器時(shí)開(kāi)發(fā)者需要實(shí)現這個(gè)方法。findClass()的缺省實(shí)現方式如下:
protected Class<?> findClass(String name)
throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
在findClass()方法內部,類(lèi)加載器需要獲取任意來(lái)源的字節碼。來(lái)源可以是文件系統,URL,數據庫,可以產(chǎn)生字節碼的另一個(gè)應用程序,及其他類(lèi)似的可以產(chǎn)生java規范的字節碼的來(lái)源。你甚至可以使用BCEL (Byte Code Engineering
Library:字節碼工程庫),它提供了運行時(shí)創(chuàng )建類(lèi)的捷徑。BCEL已經(jīng)被成功地使用在以下方面:編譯器,優(yōu)化器,混淆器,代碼產(chǎn)生器及其他分析工具。一旦字節碼被檢索,此方法就會(huì )調用defineClass()方法,此行為對不同的類(lèi)加載實(shí)例是有差異的。因此,如果兩個(gè)類(lèi)加載實(shí)例從同一個(gè)來(lái)源定義一個(gè)類(lèi),所定義的結果是不同的。
JAVA語(yǔ)言規范(Java language specification)詳細解釋了JAVA執行引擎中的類(lèi)或接口的加載(loading),鏈接(linking)或初始化(initialization)過(guò)程。
圖一顯示了一個(gè)主類(lèi)稱(chēng)為MyMainClass的應用程序。依照之前的闡述,MyMainClass.class會(huì )被AppClassLoader加載。 MyMainClass創(chuàng )建了兩個(gè)類(lèi)加載器的實(shí)例:CustomClassLoader1 和 CustomClassLoader2,他們可以從某數據源(比如網(wǎng)絡(luò ))獲取名為T(mén)arget的字節碼。這表示類(lèi)Target的類(lèi)定義不在應用程序類(lèi)路徑或擴展類(lèi)路徑。在這種情況下,如果MyMainClass想要用自定義的類(lèi)加載器加載Target類(lèi),CustomClassLoader1和CustomClassLoader2會(huì )分別獨立地加載并定義Target.class類(lèi)。這在java中有重要的意義。如果Target類(lèi)有一些靜態(tài)的初始化代碼,并且假設我們只希望這些代碼在JVM中只執行一次,而這些代碼在我們目前的步驟中會(huì )執行兩次——分別被不同的CustomClassLoaders加載并執行。如果類(lèi)Target被兩個(gè)CustomClassLoaders加載并創(chuàng )建兩個(gè)實(shí)例Target1和Target2,如圖一顯示,它們不是類(lèi)型兼容的。換句話(huà)說(shuō),在JVM中無(wú)法執行以下代碼:
Target target3 = (Target) target2;
以上代碼會(huì )拋出一個(gè)ClassCastException。這是因為JVM把他們視為分別不同的類(lèi),因為他們被不同的類(lèi)加載器所定義。這種情況當我們不是使用兩個(gè)不同的類(lèi)加載器CustomClassLoader1 和 CustomClassLoader2,而是使用同一個(gè)類(lèi)加載器CustomClassLoader的不同實(shí)例時(shí),也會(huì )出現同樣的錯誤。這些會(huì )在本文后邊用具體代碼說(shuō)明。
1 附圖: 2097hf979897.jpg (50766 字節)
圖1. 在同一個(gè)JVM中多個(gè)類(lèi)加載器加載同一個(gè)目標類(lèi)
關(guān)于類(lèi)加載、定義和鏈接的更多解釋?zhuān)垍⒖糀ndreas Schaefer的"Inside Class Loaders."
為什么我們需要我們自己的類(lèi)加載器 原因之一為開(kāi)發(fā)者寫(xiě)自己的類(lèi)加載器來(lái)控制JVM中的類(lèi)加載行為,java中的類(lèi)靠其包名和類(lèi)名來(lái)標識,對于實(shí)現了java.io.Serializable接口的類(lèi),serialVersionUID扮演了一個(gè)標識類(lèi)版本的重要角色。這個(gè)唯一標識是一個(gè)類(lèi)名、接口名、成員方法及屬性等組成的一個(gè)64位的哈希字段,而且也沒(méi)有其他快捷的方式來(lái)標識一個(gè)類(lèi)的版本。嚴格說(shuō)來(lái),如果以上的都匹配,那么則屬于同一個(gè)類(lèi)。
但是讓我們思考如下情況:我們需要開(kāi)發(fā)一個(gè)通用的執行引擎??梢詧绦袑?shí)現某一特定接口的任何任務(wù)。當任務(wù)被提交到這個(gè)引擎,首先需要加載這個(gè)任務(wù)的代碼。假設不同的客戶(hù)對此引擎提交了不同的任務(wù),湊巧,這些所有的任務(wù)都有一個(gè)相同的類(lèi)名和包名?,F在面臨的問(wèn)題就是這個(gè)引擎是否可以針對不同的用戶(hù)所提交的信息而做出不同的反應。這一情況在下文的參考一節有可供下載的代碼樣例,samepath 和 differentversions,這兩個(gè)目錄分別演示了這一概念。
圖2 顯示了文件目錄結構,有三個(gè)子目錄samepath, differentversions, 和 differentversionspush,里邊是例子:
2 附圖: xw33i9xt766e.jpg (36130 字節)
圖2. 文件夾結構組織示例
在samepath 中,類(lèi)version.Version保存在v1和v2兩個(gè)子目錄里,兩個(gè)類(lèi)具有同樣的類(lèi)名和包名,唯一不同的是下邊這行:
public void fx(){
log("this = " + this + "; Version.fx(1).");
}
V1中,日志記錄中有Version.fx(1),而在v2中則是Version.fx(2)。把這個(gè)兩個(gè)存在細微不同的類(lèi)放在一個(gè)classpath下,然后運行Test類(lèi):
set CLASSPATH=.;%CURRENT_ROOT%\v1;%CURRENT_ROOT%\v2
%JAVA_HOME%\bin\java Test
圖3顯示了控制臺輸出。我們可以看到對應著(zhù)Version.fx(1)的代碼被執行了,因為類(lèi)加載器在classpath首先看到此版本的代碼。
3 附圖: 047utg4n29dt.jpg (38120 字節)
圖3. 在類(lèi)路徑中samepath測試排在最前面的version 1
再次運行,類(lèi)路徑做如下微小改動(dòng)。
set CLASSPATH=.;%CURRENT_ROOT%\v2;%CURRENT_ROOT%\v1
%JAVA_HOME%\bin\java Test
控制臺的輸出變?yōu)閳D4。對應著(zhù)Version.fx(2)的代碼被加載,因為類(lèi)加載器在classpath中首先找到它的路徑。
4 附圖: wply7f5z7u13.jpg (35830 字節)
圖4. 在類(lèi)路徑中samepath測試排在最前面的version 2
根據以上例子可以很明顯地看出,類(lèi)加載器加載在類(lèi)路徑中被首先找到的元素。如果我們在v1和v2中刪除了version.Version,做一個(gè)非version.Version形式的.jar文件,如myextension.jar,把它放到對應java.ext.dirs的路徑下,再次執行后看到version.Version不再被AppClassLoader加載,而是被擴展類(lèi)加載器加載。如圖5所示。
5 附圖: 6ybfc43l7lt2.jpg (31302 字節)
圖5. AppClassLoader及ExtClassLoader
繼續這個(gè)例子,文件夾differentversions包含了一個(gè)RMI執行引擎,客戶(hù)端可以提供給執行引擎任何實(shí)現了common.TaskIntf接口的任務(wù)。子文件夾client1 和 client2包含了類(lèi)client.TaskImpl有個(gè)細微不同的兩個(gè)版本。兩個(gè)類(lèi)的區別在以下幾行:
static{
log("client.TaskImpl.class.getClassLoader
(v1) : " + TaskImpl.class.getClassLoader());
}
public void execute(){
log("this = " + this + "; execute(1)");
}
在client1和client2里分別有g(shù)etClassLoader(v1) 與 execute(1)和getClassLoader(v2) 與 execute(2)的的log語(yǔ)句。并且,在開(kāi)始執行引擎RMI服務(wù)器的代碼中,我們隨意地將client2的任務(wù)實(shí)現放在類(lèi)路徑的前面。
CLASSPATH=%CURRENT_ROOT%\common;%CURRENT_ROOT%\server;
%CURRENT_ROOT%\client2;%CURRENT_ROOT%\client1
%JAVA_HOME%\bin\java server.Server
如圖6,7,8的屏幕截圖,在客戶(hù)端VM,各自的client.TaskImpl類(lèi)被加載、實(shí)例化,并發(fā)送到服務(wù)端的VM來(lái)執行。從服務(wù)端的控制臺,可以明顯看到client.TaskImpl代碼只被服務(wù)端的VM執行一次,這個(gè)單一的代碼版本在服務(wù)端多次生成了許多實(shí)例,并執行任務(wù)。
6 附圖: r1c12i2ia7o3.jpg (47007 字節)
圖6. 執行引擎服務(wù)器控制臺
圖6顯示了服務(wù)端的控制臺,加載并執行兩個(gè)不同的客戶(hù)端的請求,如圖7,8所示。需要注意的是,代碼只被加載了一次(從靜態(tài)初始化塊的日志中也可以明顯看出),但對于客戶(hù)端的調用這個(gè)方法被執行了兩次。
7 附圖: siv7a6p52k6z.jpg (33862 字節)
圖7. 執行引擎客戶(hù)端 1控制臺
圖7中,客戶(hù)端VM加載了含有client.TaskImpl.class.getClassLoader(v1)的日志內容的類(lèi)TaskImpl的代碼,并提供給服務(wù)端的執行引擎。圖8的客戶(hù)端VM加載了另一個(gè)TaskImpl的代碼,并發(fā)送給服務(wù)端。
8 附圖: 4j7kax69v4x5.jpg (34782 字節)
圖8. 執行引擎客戶(hù)端 2控制臺
在客戶(hù)端的VM中,類(lèi)client.TaskImpl被分別加載,初始化,并發(fā)送到服務(wù)端執行。圖6還揭示了client.TaskImpl的代碼只在服務(wù)端的VM中加載了一次,但這“唯一的一次”卻在服務(wù)端創(chuàng )造了許多實(shí)例并執行?;蛟S客戶(hù)端1該不高興了因為并不是它的client.TaskImpl(v1)的方法調用被服務(wù)端執行了,而是其他的一些代碼。如何解決這一問(wèn)題?答案就是實(shí)現定制的類(lèi)加載器。
定制類(lèi)加載器 要較好地控制類(lèi)的加載,就要實(shí)現定制的類(lèi)加載器。所有自定義的類(lèi)加載器都應繼承自java.lang.ClassLoader。而且在構造方法中,我們也應該設置父類(lèi)加載器。然后重寫(xiě)findClass()方法。differentversionspush文件夾包含了一個(gè)叫做FileSystemClassLoader的自訂制的類(lèi)加載器。其結構如圖9所示。
9 附圖: 22cve6owje59.jpg (61382 字節)
圖9. 定制類(lèi)加載器關(guān)系
以下是在common.FileSystemClassLoader實(shí)現的主方法:
public byte[] findClassBytes(String className){
try{
String pathName = currentRoot +
File.separatorChar + className.
replace(‘.‘, File.separatorChar)
+ ".class";
FileInputStream inFile = new
FileInputStream(pathName);
byte[] classBytes = new
byte[inFile.available()];
inFile.read(classBytes);
return classBytes;
}
catch (java.io.IOException ioEx){
return null;
}
}
public Class findClass(String name)throws
ClassNotFoundException{
byte[] classBytes = findClassBytes(name);
if (classBytes==null){
throw new ClassNotFoundException();
}
else{
return defineClass(name, classBytes,
0, classBytes.length);
}
}
public Class findClass(String name, byte[]
classBytes)throws ClassNotFoundException{
if (classBytes==null){
throw new ClassNotFoundException(
"(classBytes==null)");
}
else{
return defineClass(name, classBytes,
0, classBytes.length);
}
}
public void execute(String codeName,
byte[] code){
Class klass = null;
try{
klass = findClass(codeName, code);
TaskIntf task = (TaskIntf)
klass.newInstance();
task.execute();
}
catch(Exception exception){
exception.printStackTrace();
}
}
這個(gè)類(lèi)供客戶(hù)端把client.TaskImpl(v1)轉換成字節數組,之后此字節數組被發(fā)送到RMI服務(wù)端。在服務(wù)端,一個(gè)同樣的類(lèi)用來(lái)把字節數組的內容轉換回代碼??蛻?hù)端代碼如下:
public class Client{
public static void main (String[] args){
try{
byte[] code = getClassDefinition
("client.TaskImpl");
serverIntf.execute("client.TaskImpl",
code);
}
catch(RemoteException remoteException){
remoteException.printStackTrace();
}
}
private static byte[] getClassDefinition
(String codeName){
String userDir = System.getProperties().
getProperty("BytePath");
FileSystemClassLoader fscl1 = null;
try{
fscl1 = new FileSystemClassLoader
(userDir);
}
catch(FileNotFoundException
fileNotFoundException){
fileNotFoundException.printStackTrace();
}
return fscl1.findClassBytes(codeName);
}
}
在執行引擎中,從客戶(hù)端收到的代碼被送到定制的類(lèi)加載器中。定制的類(lèi)加載器把其從字節數組定義成類(lèi),實(shí)例化并執行。需要指出的是,對每一個(gè)客戶(hù)請求,我們用類(lèi)FileSystemClassLoader的不同實(shí)例來(lái)定義客戶(hù)端提交的client.TaskImpl。而且,client.TaskImpl并不在服務(wù)端的類(lèi)路徑中。這也就意味著(zhù)當我們在FileSystemClassLoader調用findClass()方法時(shí),findClass()調用內在的defineClass()方法。類(lèi)client.TaskImpl被特定的類(lèi)加載器實(shí)例所定義。因此,當FileSystemClassLoader的一個(gè)新的實(shí)例被使用,類(lèi)又被重新定義為字節數組。因此,對每個(gè)客戶(hù)端請求類(lèi)client.TaskImpl被多次定義,我們就可以在相同執行引擎JVM中執行不同的client.TaskImpl的代碼。
public void execute(String codeName, byte[] code)throws RemoteException{
FileSystemClassLoader fileSystemClassLoader = null;
try{
fileSystemClassLoader = new FileSystemClassLoader();
fileSystemClassLoader.execute(codeName, code);
}
catch(Exception exception){
throw new RemoteException(exception.getMessage());
}
}
示例在differentversionspush文件夾下。服務(wù)端和客戶(hù)端的控制臺界面分別如圖10,11,12所示:
10 附圖: sth3br076g80.jpg (47719 字節)
圖10. 定制類(lèi)加載器執行引擎
圖10顯示的是定制的類(lèi)加載器控制臺。我們可以看到client.TaskImpl的代碼被多次加載。實(shí)際上針對每一個(gè)客戶(hù)端,類(lèi)都被加載并初始化。
11 附圖: 5975e618h5l8.jpg (38098 字節)
圖11. 定制類(lèi)加載器,客戶(hù)端1
圖11中,含有client.TaskImpl.class.getClassLoader(v1)的日志記錄的類(lèi)TaskImpl的代碼被客戶(hù)端的VM加載,然后送到服務(wù)端。圖12 另一個(gè)客戶(hù)端把包含有client.TaskImpl.class.getClassLoader(v1)的類(lèi)代碼加載并送往服務(wù)端。
12 附圖: t5690273m6d9.jpg (37328 字節)
圖12. 定制類(lèi)加載器,客戶(hù)端1
這段代碼演示了我們如何利用不同的類(lèi)加載器實(shí)例來(lái)在同一個(gè)VM上執行不同版本的代碼。
J2EE的類(lèi)加載器 J2EE的服務(wù)器傾向于以一定間隔頻率,丟棄原有的類(lèi)并重新載入新的類(lèi)。在某些情況下會(huì )這樣執行,而有些情況則不。同樣,對于一個(gè)web服務(wù)器如果要丟棄一個(gè)servlet實(shí)例,可能是服務(wù)器管理員的手動(dòng)操作,也可能是此實(shí)例長(cháng)時(shí)間未相應。當一個(gè)JSP頁(yè)面被首次請求,容器會(huì )把此JSP頁(yè)面翻譯成一個(gè)具有特定形式的servlet代碼。一旦servlet代碼被創(chuàng )建,容器就會(huì )把這個(gè)servlet翻譯成class文件等待被使用。對于提交給容器的每次請求,容器都會(huì )首先檢查這個(gè)JSP文件是否剛被修改過(guò)。是的話(huà)就重新翻譯此文件,這可以確保每次的請求都是及時(shí)更新的。企業(yè)級的部署方案以.ear, .war, .rar等形式的文件,同樣需要重復加載,可能是隨意的也可能是依照某種配置方案定期執行。對所有的這些情況——類(lèi)的加載、卸載、重新加載……全部都是建立在我們控制應用服務(wù)器的類(lèi)加載機制的基礎上的。實(shí)現這些需要擴展的類(lèi)加載器,它可以執行由其自身所定義的類(lèi)。Brett Peterson已經(jīng)在他的文章 Understanding J2EE Application Server Class Loading Architectures給出了J2EE應用服務(wù)器的類(lèi)加載方案的詳細說(shuō)明,詳見(jiàn)網(wǎng)站TheServerSide.com。
結要 本文探討了類(lèi)載入到虛擬機是如何進(jìn)行唯一標識的,以及類(lèi)如果存在同樣的類(lèi)名和包名時(shí)所產(chǎn)生的問(wèn)題。因為沒(méi)有一個(gè)直接可用的類(lèi)版本管理機制,所以如果我們要按自己的意愿來(lái)加載類(lèi)時(shí),需要自己訂制類(lèi)加載器來(lái)擴展其行為。我們可以利用許多J2EE服務(wù)器所提供的“熱部署”功能來(lái)重新加載一個(gè)新版本的類(lèi),而不改動(dòng)服務(wù)器的VM。即使不涉及應用服務(wù)器,我們也可以利用定制類(lèi)加載器來(lái)控制java應用程序載入類(lèi)時(shí)的具體行為。
參考
●本文的源碼:
13 附件:
2005_10_19_004826_dgTsHmDOPq.zip (45351 字節)
●JDK 1.5 API文檔
●Java語(yǔ)言規范
●Java tutorial 中的"Understanding Extension Class Loading "
●ONJava 中的"Inside Class Loaders"
●ONJava 中的"Inside Class Loaders: Debugging"
●JavaWorld中的"What version is your Java code?"
●TheServerSide中的" Understanding J2EE Application Server Class Loading Architectures"
●字節碼工程庫
●Server-Based Java Programming,作者:Ted Neward
Binildas Christudas 是Infosys的Communication Service Providers Practice (CSP)中的高級技術(shù)架構師,也是Sun認證企業(yè)架構師及微軟認證專(zhuān)家。