![深入了解類加載器_第1頁](http://file2.renrendoc.com/fileroot_temp3/2021-10/23/996edf42-5f02-4de5-842c-c0db504b3914/996edf42-5f02-4de5-842c-c0db504b39141.gif)
![深入了解類加載器_第2頁](http://file2.renrendoc.com/fileroot_temp3/2021-10/23/996edf42-5f02-4de5-842c-c0db504b3914/996edf42-5f02-4de5-842c-c0db504b39142.gif)
![深入了解類加載器_第3頁](http://file2.renrendoc.com/fileroot_temp3/2021-10/23/996edf42-5f02-4de5-842c-c0db504b3914/996edf42-5f02-4de5-842c-c0db504b39143.gif)
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、深入探討Java類加載器類加載器是Java語言的一個創(chuàng)新,也是 Java語言流行的重要原因之一。它 使得Java類可以被動態(tài)加載到Java虛擬機中并執(zhí)行。類加載器從JDK 1.0就出現了,最初是為了滿足Java Applet的需要而開發(fā)出來的。Java Applet需 要從遠程下載Java類文件到瀏覽器中并執(zhí)行?,F在類加載器在Web容器和OSGi中得到了廣泛的使用。一般來說,Java應用的開發(fā)人員不需要直接同類 加載器進行交互。Java虛擬機默認的行為就已經足夠滿足大多數情況的需求了。 不過如果遇到了需要與類加載器進行交互的情況,而對類加載器的機制又不是很 了解的話,就很容易花大量的時間去調試
2、ClassNotFou ndExceptio n和NoClassDefFoundError等異常。本文將詳細介紹 Java的類加載器,幫助讀者深刻理解Java語言中的這個重要概念。下面首先介紹一些相關的基本 概念。類加載器基本概念顧名思義,類加載器(class loader )用來加載Java類到Java虛擬機中。一 般來說,Java虛擬機使用Java類的方式如下:Java源程序(.java文件)在 經過Java編譯器編譯之后就被轉換成 Java字節(jié)代碼(.class文件)。類加 載器負責讀取Java字節(jié)代碼,并轉換成類的一個實例。每個這樣的實例用來表示一個Java類。通過此實例的newlns
3、tance()方法就可以創(chuàng)建出該類的一個對象。實際的情況可能更加復雜,比如Java字節(jié)代碼可能是通過工具動態(tài)生成的,也可能是通過網絡下載的。基本上所有的類加載器都是java.la ng.ClassLoader類的一個實例。下面詳細介紹這個Java類。javaan g.ClassLoader類介紹java.la ng.ClassLoader類的基本職責就是根據一個指定的類的名稱,找到或者生成其對應的字節(jié)代碼,然后從這些字節(jié)代碼中定義出一個Java類,即類的一個實例。除此之外,ClassLoader還負責加載Java應用所需的資源,如圖像文件和配置文件等。不過本文只討論其加載類的 功能。為了完成加
4、載類的這個職責,ClassLoader 提供了一系列的方法,比較 重要的方法如 所示。關于這些方法的細節(jié)會在下面進行介紹。表1. ClassLoader中與加載類相關的方法方法說明getPare nt()返回該類加載器的父類加載器。方法說明loadClass(Stri ng n ame)加載名稱為name的類,返回的結果是 java.la ng.Class類的實例。fin dClass(Stri ng n ame)查找名稱為name的類,返回的結果是 java.la ng.Class類的實例。fin dLoadedClass(Stri ng n ame)查找名稱為name的已經被加載過的類,返
5、回的結果是 java.la ng.Class類的實例。defi neClass(Stri ng n ame, byte把字節(jié)數組b中的內容轉換成Java類,返回的結果b, int off, int len)是final的。resolveClass(Class c)鏈接指定的Java類。對于中給出的方法,表示類名稱的name參數的值是類的二進制名稱。需要注 意的是內部類的表示,如,做進一步的說明。下面介紹類加載器的樹狀組織結 構。類加載器的樹狀組織結構Java中的類加載器大致可以分成兩類,一類是系統(tǒng)提供的,另外一類則是由Java應用開發(fā)人員編寫的。系統(tǒng)提供的類加載器主要有下面三個:引導類加載器(
6、bootstrap class loader ):它用來加載 Java的核心庫,是用原生代碼來實現的,并不繼承自*擴展類加載器(extensions class loader ):它用來加載 Java的擴展庫。Java虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java類。系統(tǒng)類加載器(system class loader ):它根據Java應用的類路徑(CLASSPATH )來加載Java類。一般來說,Java應用的類都是 由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。除了系統(tǒng)提供的類加載器以外,開發(fā)人員可
7、以通過繼承,以滿足一些特殊的需求。除了引導類加載器之外,所有的類加載器都有一個父類加載器。通過中給出的getParent()方法可以得到。對于系統(tǒng)提供的類加載器來說,系統(tǒng)類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器; 對于開發(fā)人員編寫的類加載器來說,其父類加載器是加載此類加載器Java類的類加載器。因為類加載器 Java類如同其它的Java類一樣,也是要由類加 載器來加載的。一般來說,開發(fā)人員編寫的類加載器的父類加載器是系統(tǒng)類加載 器。類加載器通過這種方式組織起來,形成樹狀結構。樹的根節(jié)點就是引導類加 載器。中給出了一個典型的類加載器樹狀組織結構示意圖,其中的箭
8、頭指向的是父類加載器。圖1.類加載器樹狀組織結構示意圖演示了類加載器的樹狀組織結構。 清單1.演示類加載器的樹狀組織結構 public class ClassLoaderTree public static void main (Stri ng args) ClassLoader loader =;while (loader != n ull) loader = loader.getPare nt();每個Java類都維護著一個指向定義它的類加載器的引用,通 過getClassLoader() 方法就可以獲取到此引用。中通過遞歸調用 getPare nt()方法來輸出全部的父類加載器。的運行結
9、果如所示清單2.演示類加載器的樹狀組織結構的運行結果如所示,第一個輸出的是ClassLoaderTree類的類加載器,即系統(tǒng)類加載器。它是;第二個輸出的是擴展類加載器,是,這是由于有些JDK的實現對于父類加載器是引導類加載器的情況,getParent()方法返回null 。在了解了類加載器的樹狀組織結構之后,下面介紹類加載器的代理模式。類加載器的代理模式類加載器在嘗試自己去查找某個類的字節(jié)代碼并定義它時,會先代理給其父類加載器,由父類加載器先去嘗試加載這個類,依次類推。在介紹代理模式背后的動機之前,首先需要說明一下Java虛擬機是如何判定兩個Java類是相同的。Java虛擬機不僅要看類的全名是
10、否相同,還要看加載此類的類加載器是否一樣。 只有兩者都相同的情況,才認為兩個類是相同的。即便是同樣的字節(jié)代碼,被不 同的類加載器加載之后所得到的類,也是不同的。比如一個Java類,編譯之后生成了字節(jié)代碼文件Sample.class 。兩個不同的類加載器ClassLoaderA 和ClassLoaderB分別讀取了這個Sample.class文件,并定義出兩個Java虛擬機來說,它們是不同的類。試圖對這兩個類的對象進行相互賦值, 會拋出運行時異常ClassCastException 。下面通過示例來具體說明。中給出 了 Java 類 清單3.類package com.example;public
11、 class Sample private Sample in sta nee;public void setSample(Object in sta nee) this.i nsta nee = (Sample) in sta nee;如所示,setSample 接受一個,并且會把該參數強制轉換成Java類是否相同的代碼如所示。清單4.測試Java類是否相同public void testClassIde ntity() Stri ng classDataRootPath = C:workspaceClassloaderclassData;FileSystemClassLoader fscl
12、l = new FileSystemClassLoader(classDataRootPath);FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);Stri ng className =;try Class class1 = fscl1.loadClass(className);Object obj1 = class1. newIn sta nce();Class class2 = fscl2 .l oadClass(className);Object obj2 = class2 .newIn
13、sta nce();Method setSampleMethod = class1.getMethod(setSample,;setSampleMethod.invoke(obj1, obj2); catch (Excepti on e) e.pri ntStackTrace();中使用了類FileSystemClassLoader 的兩個不同實例來分別加載類,得到 了兩個不同的,接著通過newInstance()方法分別生成了兩個類的對象obj1和obj2,最后通過Java的反射API在對象obj1上調用方法setSample , 試圖把對象obj2賦值給obj1內部的instanee 對象
14、。的運行結果如 所示。 清單5.測試Java類是否相同的運行結果at Method)atatatatatCaused by:cannot be cast toat.6 more從給出的運行結果可以看到,運行時拋出了objl和obj2的類的名字相同,但是這兩個類是由不同的類加載器實例來加載的,因此不被Java虛擬機認為是相同的。了解了這一點之后,就可以理解代理模式的設計動機了。代理模式是為了保證 Java核心庫的類型安全。所有Java應用都至少需要引用,也就是說在運行的時候,Java虛擬機中。如果這個加載過程由Java應用自己的類加載器來完成的話,很可能就存在多個版本的,而且這些類之間是不兼容的
15、。通過代理模 式,對于Java核心庫的類的加載工作由引導類加載器來統(tǒng)一完成,保證了 Java應用所使用的都是同一個版本的Java核心庫的類,是互相兼容的。不同的類加載器為相同名稱的類創(chuàng)建了額外的名稱空間。相同名稱的類可以并存 在Java虛擬機中,只需要用不同的類加載器來加載它們即可。不同類加載器 加載的類之間是不兼容的,這就相當于在 Java虛擬機內部創(chuàng)建了一個個相互 隔離的Java類空間。這種技術在許多框架中都被用到,后面會詳細介紹。下面具體介紹類加載器加載類的詳細過程。加載類的過程在前面介紹類加載器的代理模式的時候,提到過類加載器會首先代理給其它類加 載器來嘗試加載某個類。這就意味著真正完
16、成類的加載工作的類加載器和啟動這 個加載過程的類加載器,有可能不是同一個。真正完成類的加載工作是通過調 用defin eClass來實現的;而啟動類的加載過程是通過調用loadClass 來實現的。前者稱為一個類的定義加載器(defining loader ),后者稱為初始加載器(initiating loader )。在Java虛擬機判斷兩個類是否相同的時候,使用的是類 的定義加載器。也就是說,哪個類加載器啟動類的加載過程并不重要,重要的是最終定義這個類的加載器。兩種類加載器的關聯之處在于:一個類的定義加載器 是它引用的其它類的初始加載器。如類,則由類方法loadClass() 拋出的是;方
17、法defineClass()拋出的是類加載器在成功加載某個類之后,會把得到的,類加載器會直接使用緩存的類的實例,而不會嘗試再次加載。也就是說,對于一個類加載器實例來說,相同全 名的類只加載一次,即loadClass方法不會被重復調用。下面討論另外一種類加載器:線程上下文類加載器。線程上下文類加載器線程上下文類加載器(con text class loader )是從JDK 1.2開始引入的。類 getContextClassLoader()和setCo ntextClassLoader(ClassLoader cl)用來獲取和設置線程的上下文類加載器。如果沒有通過 setCo ntextCla
18、ssLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java應用運行的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運行的代碼可以通過此類加載器來加載類和資源。前面提到的類加載器的代理模式并不能解決Java應用開發(fā)中會遇到的類加載器的全部問題。Java提供了很多服務提供者接口 ( Service Provider In terface , SPI),允許第三方為這些接口提供實現。常見的SPI有JDBC、JCE、JNDI、JAXP和JBI等。這些 SPI的接口由Java核心庫來提供,如 JAXP的SPI 接口定義包含在 SPI的實現代碼很可能是
19、作為 Java應用所依賴的jar包被 包含進來,可以通過類路徑(CLASSPATH )來找到,如實現了 JAXP SPI的所 包含的jar包。SPI接口中的代碼經常需要加載具體的實現類。如JAXP中的 newInstance()方法用來生成一個新的 DocumentBuilderFactory的實例。這里的實例的真正的類是繼承自,由SPI的實現所提供的。如在Apache Xerces中,實現的類是,SPI的接口是Java核心庫的一部分,是由引導類 加載器來加載的;SPI實現的Java類一般是由系統(tǒng)類加載器來加載的。引導 類加載器是無法找到 SPI的實現類的,因為它只加載 Java的核心庫。它也
20、不 能代理給系統(tǒng)類加載器,因為它是系統(tǒng)類加載器的祖先類加載器。也就是說,類 加載器的代理模式無法解決這個問題。線程上下文類加載器正好解決了這個問題。如果不做任何的設置,Java應用的線程的上下文類加載器默認就是系統(tǒng)上下文類加載器。在SPI接口的代碼中使用線程上下文類加載器,就可以成功的加載到SPI實現的類。線程上下文類加載器在很多SPI的實現中都會用到。下面介紹另外一種加載類的方法:Class.forName 。Class.forNameClass.forName是一個靜態(tài)方法,同樣可以用來加載類。該方法有兩種形式:Class.forName(Stri ng n ame, boolea n i
21、n itialize, ClassLoader loader)和 Class.forName(String className)。第一種形式的參數name表示的是類的全名;initialize表示是否初始化類;loader 表示加載時使用的類加載器。第二種形式則相當于設置了參數ini tialize 的值為true ,loader的值為當前類的類加載器。Class.forName 的一個很常見的用法是在加載數據庫驅動的時候。如Class.forName).newlnstance()用來加載 Apache Derby 數據庫的驅動。在介紹完類加載器相關的基本概念之后,下面介紹如何開發(fā)自己的類加載
22、器。開發(fā)自己的類加載器雖然在絕大多數情況下,系統(tǒng)默認提供的類加載器實現已經可以滿足需求。但是在某些情況下,您還是需要為應用開發(fā)出自己的類加載器。比如您的應用通過網絡來傳輸Java類的字節(jié)代碼,為了保證安全性,這些字節(jié)代碼經過了加密處 理。這個時候您就需要自己的類加載器來從某個網絡地址上讀取加密后的字節(jié)代 碼,接著進行解密和驗證,最后定義出要在Java虛擬機中運行的類來。下面將通過兩個具體的實例來說明類加載器的開發(fā)。文件系統(tǒng)類加載器第一個類加載器用來加載存儲在文件系統(tǒng)上的Java字節(jié)代碼。完整的實現如所示。清單6.文件系統(tǒng)類加載器public class FileSystemClassLoade
23、r exte nds ClassLoader private String rootDir;public FileSystemClassLoader(String rootDir) this.rootDir = rootDir;protected Class fin dClass(Stri ng n ame) throws ClassNotFo un dExcepti on byte classData = getClassData (n ame);if (classData = n ull) throw new ClassNotFo un dExcepti on ();else retu r
24、n defi neClass (n ame, classData, 0, classData .len gth);private byte getClassData(String className) Stri ng path = classNameToPath(className);try In putStream ins = new FileI nputStream(path);ByteArrayOutputStream baos = new ByteArrayOutputStream();int bufferSize = 4096;byte buffer = new bytebuffer
25、Size;int bytesNumRead = 0;while (bytesNumRead = ins.read(buffer) != -1) baos.write(buffer, 0, bytesNumRead);retu rn baos.toByteArray(); catch (IOExcepti on e) e.pri ntStackTrace();return n ull;private Stri ng classNameToPath(Stri ng className) retu rn rootDir + File.separatorChar+ className.replace(
26、., File.separatorChar) + .class;如 所示,類FileSystemClassLoader繼承自類中列出的,一般來說,自己開發(fā)的類加載器只需要覆寫fin dClass(Stri ng name)方法即可。fin dLoadedClass()方法來檢查該類是否已經被加載過;如果沒有加載過的話,會調用父類加載器的loadClass()方法來嘗試加載該類;如果父類加載器無法加載該類的話,就調用fin dClass()方法來查找該類。因此,為了保證類加載器都正確實現代理模式,在開發(fā)自己的類加載器時,最好不要覆 寫loadClass()方法,而是覆寫findClass()方法
27、。類FileSystemClassLoader 的findClass()方法首先根據類的全名在硬盤上查找類的字節(jié)代碼文件(.class文件),然后讀取該文件內容,最后通 過defineClass()方法來把這些字節(jié)代碼轉換成網絡類加載器下面將通過一個網絡類加載器來說明如何通過類加載器來實現組件的動態(tài)更新。 即基本的場景是:Java字節(jié)代碼(.class)文件存放在服務器上,客戶端通過 網絡的方式獲取字節(jié)代碼并執(zhí)行。當有版本更新的時候,只需要替換掉服務器上 保存的文件即可。通過類加載器可以比較簡單的實現這種需求。類NetworkClassLoader負責通過網絡下載 Java類字節(jié)代碼并定義出J
28、ava類。它的實現與FileSystemClassLoader 類似。在通過NetworkClassLoader加載了某個版本的類之后,一般有兩種做法來使用它。第一種做法是使用Java反射API。另外一種做法是使用接口。需要注意的是, 并不能直接在客戶端代碼中引用從服務器上下載的類,因為客戶端代碼的類加載器找不到這些類。使用Java反射API可以直接調用Java類的方法。而使用 接口的做法則是把接口的類放在客戶端中, 從服務器上加載實現此接口的不同版 本的類。在客戶端通過相同的接口來使用這些實現類。網絡類加載器的具體代碼見。在介紹完如何開發(fā)自己的類加載器之后,下面說明類加載器和 Web容器的關
29、系,類加載器與Web容器對于運行在Java EE?容器中的 Web應用來說,類加載器的實現方式與一般 的Java應用有所不同。不同的 Web容器的實現方式也會有所不同。以 Apache Tomcat來說,每個 Web應用都有一個對應的類加載器實例。該類加 載器也使用代理模式,所不同的是它是首先嘗試去加載某個類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的。這是Java Servlet規(guī)范中的推薦做法,其目的是使得 Web應用自己的類的優(yōu)先級高于 Web容器提 供的類。這種代理模式的一個例外是:Java核心庫的類是不在查找范圍之內的。 這也是為了保證Java核心庫的類型安全。絕大
30、多數情況下,Web應用的開發(fā)人員不需要考慮與類加載器相關的細節(jié)。下 面給出幾條簡單的原則:每個 Web應用自己的Java類文件和使用的庫的jar包,分別放在 WEB-INF/classes禾口 WEB-INF/lib 目錄下面。多個應用共享的Java類文件和jar包,分別放在 Web容器指定的由所有 Web應用共享的目錄下面。.當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。在介紹完類加載器與 Web容器的關系之后,下面介紹它與OSGi的關系。類加載器與OSGiOSGi?是Java上的動態(tài)模塊系統(tǒng)。它為開發(fā)人員提供了面向服務和基于組件 的運行環(huán)境,并提供標準的方
31、式用來管理軟件的生命周期。OSGi已經被實現和部署在很多產品上,在開源社區(qū)也得到了廣泛的支持。Eclipse就是基于OSGi技術來構建的。OSGi中的每個模塊(bundle )都包含Java包和類。模塊可以聲明它所依賴的 需要導入(import)的其它模塊的 Java包和類(通過Import-Package ), 也可以聲明導出(export)自己的包和類,供其它模塊使用(通過Export-Package )。也就是說需要能夠隱藏和共享一個模塊中的某些Java包和類。這是通過 OSGi特有的類加載器機制來實現的。OSGi中的每個模塊 都有對應的一個類加載器。它負責加載模塊自己包含的Java包和類。當它需要加載Java核心庫的類時(以java開頭的包和類),它會代理給父類加載器(通常是啟動類加載器)來完成。當它需要加載所導入的 Java類時,它會代 理給導出此Java類的模塊來完成加載。模塊也可以顯式的聲明某些 Java包 和類,必須由父類加載器來加載。只需要設置系統(tǒng)屬性假設有兩個模塊bundleA和bundleB,它們都有自己對應的類加載器
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024民間借貸合同范本(28篇)
- 2025年農村子女撫養(yǎng)費用分擔協(xié)議
- 2025年供電企業(yè)與用戶用電合作協(xié)議
- 2025年共同策劃健身房合作合同書
- 企業(yè)勞動合同協(xié)議2025年
- 2025年企業(yè)員工勞動合同補充協(xié)議范本
- 2025年鋁合金預拉伸厚板和蒙皮鋁合金板項目立項申請報告模范
- 2025年高性能陶瓷復合材料項目立項申請報告模板
- 2025年企業(yè)變更代理協(xié)議
- 2025年漲緊輪項目提案報告模板
- 中國人口研究專題報告-中國2025-2100年人口預測與政策建議-西南財經大學x清華大學-202501
- 建筑工程安全與管理
- 幼兒園開學教師安全知識培訓
- 2024年山東水利職業(yè)學院高職單招職業(yè)技能測驗歷年參考題庫(頻考版)含答案解析
- 遼寧省名校聯盟2025年高三1月份聯合考試 語文試卷(含答案詳解)
- 工廠廠區(qū)道路拆除實施方案
- 25版六年級寒假特色作業(yè)
- 浙江省杭州市9+1高中聯盟2025屆高三一診考試英語試卷含解析
- 2025教科版一年級科學下冊教學計劃
- 中學生勞動安全課件
- 旅游行業(yè)智慧旅游營銷策略與方案
評論
0/150
提交評論