一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

類加載器

 昵稱103364 2010-03-02

類加載器是 Java 語(yǔ)言的一個(gè)創(chuàng)新,也是 Java 語(yǔ)言流行的重要原因之一。它使得 Java 類可以被動(dòng)態(tài)加載到 Java 虛擬機(jī)中并執(zhí)行。類加載器從 JDK 1.0 就出現(xiàn)了,最初是為了滿足 Java Applet 的需要而開發(fā)出來(lái)的。Java Applet 需要從遠(yuǎn)程下載 Java 類文件到瀏覽器中并執(zhí)行。現(xiàn)在類加載器在 Web 容器和 OSGi 中得到了廣泛的使用。一般來(lái)說(shuō),Java 應(yīng)用的開發(fā)人員不需要直接同類加載器進(jìn)行交互。Java 虛擬機(jī)默認(rèn)的行為就已經(jīng)足夠滿足大多數(shù)情況的需求了。不過(guò)如果遇到了需要與類加載器進(jìn)行交互的情況,而對(duì)類加載器的機(jī)制又不是很了解的話,就很容易花大量 的時(shí)間去調(diào)試 ClassNotFoundExceptionNoClassDefFoundError 等異常。本文將詳細(xì)介紹 Java 的類加載器,幫助讀者深刻理解 Java 語(yǔ)言中的這個(gè)重要概念。下面首先介紹一些相關(guān)的基本概念。

類加載器基本概念

顧名思義,類加載器(class loader)用來(lái)加載 Java 類到 Java 虛擬機(jī)中。一般來(lái)說(shuō),Java 虛擬機(jī)使用 Java 類的方式如下:Java 源程序(.java 文件)在經(jīng)過(guò) Java 編譯器編譯之后就被轉(zhuǎn)換成 Java 字節(jié)代碼(.class 文件)。類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼,并轉(zhuǎn)換成 java.lang.Class 類的一個(gè)實(shí)例。每個(gè)這樣的實(shí)例用來(lái)表示一個(gè) Java 類。通過(guò)此實(shí)例的 newInstance()方法就可以創(chuàng)建出該類的 一個(gè)對(duì)象。實(shí)際的情況可能更加復(fù)雜,比如 Java 字節(jié)代碼可能是通過(guò)工具動(dòng)態(tài)生成的,也可能是通過(guò)網(wǎng)絡(luò)下載的。

基本上所有的類加載器都是 java.lang.ClassLoader 類的一個(gè)實(shí)例。下面詳細(xì)介紹這個(gè) Java 類。

java.lang.ClassLoader 類介紹

java.lang.ClassLoader 類的基本職責(zé)就是根據(jù)一個(gè)指定的類的名稱,找到或者生成其對(duì)應(yīng)的字節(jié)代碼,然后從這些字節(jié)代碼中定義出一個(gè) Java 類,即 java.lang.Class 類的一個(gè)實(shí)例。除此之外,ClassLoader 還負(fù)責(zé)加載 Java 應(yīng)用所需的資源,如圖像文件和配置文件等。不過(guò)本文只討論其加載類的功能。為了完成加載類的這個(gè)職責(zé),ClassLoader 提供了一系列的方法,比較重要的方法如 表 1 所示。關(guān)于這些方法的細(xì)節(jié)會(huì)在下面進(jìn)行介紹。


表 1. ClassLoader 中與加載類相關(guān)的方法
方法說(shuō)明
getParent() 返回該類加載器的父類加載器。
loadClass(String name) 加載名稱為 name 的類,返回的結(jié)果是 java.lang.Class 類的實(shí)例。
findClass(String name) 查找名稱為 name 的類,返回的結(jié)果是 java.lang.Class 類的實(shí)例。
findLoadedClass(String name) 查找名稱為 name 的已經(jīng)被加載過(guò)的類,返回的結(jié)果是 java.lang.Class 類的實(shí)例。
defineClass(String name, byte[] b, int off, int len) 把字節(jié)數(shù)組 b 中的內(nèi)容轉(zhuǎn)換成 Java 類,返回的結(jié)果是 java.lang.Class 類的實(shí)例。這個(gè)方法被聲明為 final 的。
resolveClass(Class<?> c) 鏈接指定的 Java 類。

對(duì)于 表 1 中給出的方法,表示類名稱的 name 參數(shù)的值是類的二進(jìn)制名稱。需要注意的是內(nèi)部類的表示,如 com.example.Sample$1com.example.Sample$Inner 等表示方式。這些方法會(huì)在下面介紹類加載器的工作機(jī)制時(shí),做進(jìn)一步的說(shuō)明。下面介紹類加載器的樹狀組織結(jié)構(gòu)。

類加載器的樹狀組織結(jié)構(gòu)

Java 中的類加載器大致可以分成兩類,一類是系統(tǒng)提供的,另外一類則是由 Java 應(yīng)用開發(fā)人員編寫的。系統(tǒng)提供的類加載器主要有下面三個(gè):

  • 引導(dǎo)類加載器(bootstrap class loader):它用來(lái)加載 Java 的核心庫(kù),是用原生代碼來(lái)實(shí)現(xiàn)的,并不繼承自 java.lang.ClassLoader。
  • 擴(kuò)展類加載器(extensions class loader):它用來(lái)加載 Java 的擴(kuò)展庫(kù)。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫(kù)目錄。該類加載器在此目錄里面查找并加載 Java 類。
  • 系統(tǒng)類加載器(system class loader):它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH)來(lái)加載 Java 類。一般來(lái)說(shuō),Java 應(yīng)用的類都是由它來(lái)完成加載的。可以通過(guò) ClassLoader.getSystemClassLoader() 來(lái)獲取它。

除了系統(tǒng)提供的類加載器以外,開發(fā)人員可以通過(guò)繼承 java.lang.ClassLoader 類的方式實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求。

除了引導(dǎo)類加載器之外,所有的類加載器都有一個(gè)父類加載器。通過(guò) 表 1 中給出的 getParent() 方法可以得到。對(duì)于系統(tǒng)提供的類加載器來(lái)說(shuō),系統(tǒng)類加載器的父類加載器是擴(kuò)展類加載器,而擴(kuò)展類加載器的父類加載器是引導(dǎo)類加載器;對(duì)于開發(fā)人員編寫的類 加載器來(lái)說(shuō),其父類加載器是加載此類加載器 Java 類的類加載器。因?yàn)轭惣虞d器 Java 類如同其它的 Java 類一樣,也是要由類加載器來(lái)加載的。一般來(lái)說(shuō),開發(fā)人員編寫的類加載器的父類加載器是系統(tǒng)類加載器。類加載器通過(guò)這種方式組織起來(lái),形成樹狀結(jié)構(gòu)。樹的根 節(jié)點(diǎn)就是引導(dǎo)類加載器。圖 1 中給出了一個(gè)典型的類加載器樹狀組織結(jié)構(gòu)示意圖,其中的箭頭指向的是父類加載器。


圖 1. 類加載器樹狀組織結(jié)構(gòu)示意圖
類加載器樹狀組織結(jié)構(gòu)示意圖

代 碼清單 1 演示了類加載器的樹狀組織結(jié)構(gòu)。


清單 1. 演示類加載器的樹狀組織結(jié)構(gòu)
public class ClassLoaderTree { 

public static void main(String[] args) {
ClassLoader loader = ClassLoaderTree.class.getClassLoader();
while (loader != null) {
System.out.println(loader.toString());
loader = loader.getParent();
}
}
}

每個(gè) Java 類都維護(hù)著一個(gè)指向定義它的類加載器的引用,通過(guò) getClassLoader() 方法就可以獲取到此引用。代 碼清單 1 中通過(guò)遞歸調(diào)用 getParent() 方法來(lái)輸出全部的父類加載器。代 碼清單 1 的運(yùn)行結(jié)果如 代 碼清單 2 所示。


清單 2. 演示類加載器的樹狀組織結(jié)構(gòu)的運(yùn)行結(jié)果
sun.misc.Launcher$AppClassLoader@9304b1 
sun.misc.Launcher$ExtClassLoader@190d11

代 碼清單 2 所示,第一個(gè)輸出的是 ClassLoaderTree 類的類加載器,即系統(tǒng)類加載器。它是 sun.misc.Launcher$AppClassLoader 類的實(shí)例;第二個(gè)輸出的是擴(kuò)展類加載器,是 sun.misc.Launcher$ExtClassLoader 類的實(shí)例。需要注意的是這里并沒有輸出引導(dǎo)類加載器,這是由于有些 JDK 的實(shí)現(xiàn)對(duì)于父類加載器是引導(dǎo)類加載器的情況,getParent() 方法返回 null。

在了解了類加載器的樹狀組織結(jié)構(gòu)之后,下面介紹類加載器的代理模式。

類加載器的代理模式

類加載器在嘗試自己去查找某個(gè)類的字節(jié)代碼并定義它時(shí),會(huì)先代理給其父類加載器,由父類加載器先去嘗試加載這個(gè)類,依次類推。在介紹代理模式 背后的動(dòng)機(jī)之前,首先需要說(shuō)明一下 Java 虛擬機(jī)是如何判定兩個(gè) Java 類是相同的。Java 虛擬機(jī)不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認(rèn)為兩個(gè)類是相同的。即便是同樣的字節(jié)代碼,被不同的類加 載器加載之后所得到的類,也是不同的。比如一個(gè) Java 類 com.example.Sample,編譯之后生成了字節(jié)代 碼文件 Sample.class。兩個(gè)不同的類加載器 ClassLoaderAClassLoaderB 分別讀取了這個(gè) Sample.class 文件,并定義出兩個(gè) java.lang.Class 類的實(shí)例來(lái)表示這個(gè)類。這兩個(gè)實(shí)例是不相同的。對(duì)于 Java 虛擬機(jī)來(lái)說(shuō),它們是不同的類。試圖對(duì)這兩個(gè)類的對(duì)象進(jìn)行相互賦值,會(huì)拋出運(yùn)行時(shí)異常 ClassCastException。 下面通過(guò)示例來(lái)具體說(shuō)明。代 碼清單 3 中給出了 Java 類 com.example.Sample


清單 3. com.example.Sample 類
package com.example; 

public class Sample {
private Sample instance;

public void setSample(Object instance) {
this.instance = (Sample) instance;
}
}

代 碼清單 3 所示,com.example.Sample 類的方法 setSample 接受一個(gè) java.lang.Object 類型的參數(shù),并且會(huì)把該參數(shù)強(qiáng)制轉(zhuǎn)換成 com.example.Sample 類型。測(cè)試 Java 類是否相同的代碼如 代 碼清單 4 所示。


清單 4. 測(cè)試 Java 類是否相同
public void testClassIdentity() { 
String classDataRootPath = "C:\\workspace\\Classloader\\classData";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
String className = "com.example.Sample";
try {
Class<?> class1 = fscl1.loadClass(className);
Object obj1 = class1.newInstance();
Class<?> class2 = fscl2.loadClass(className);
Object obj2 = class2.newInstance();
Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
setSampleMethod.invoke(obj1, obj2);
} catch (Exception e) {
e.printStackTrace();
}
}

代 碼清單 4 中使用了類 FileSystemClassLoader 的兩個(gè)不同實(shí)例來(lái)分別加載類 com.example.Sample, 得到了兩個(gè)不同的 java.lang.Class 的實(shí)例,接著通過(guò) newInstance() 方法分別生成了兩個(gè)類的對(duì)象 obj1obj2,最后通過(guò) Java 的反射 API 在對(duì)象 obj1 上調(diào)用方法 setSample,試圖把對(duì)象 obj2 賦值給 obj1 內(nèi)部的 instance 對(duì)象。代 碼清單 4 的運(yùn)行結(jié)果如 代 碼清單 5 所示。


清單 5. 測(cè)試 Java 類是否相同的運(yùn)行結(jié)果
java.lang.reflect.InvocationTargetException 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26)
at classloader.ClassIdentity.main(ClassIdentity.java:9)
Caused by: java.lang.ClassCastException: com.example.Sample
cannot be cast to com.example.Sample
at com.example.Sample.setSample(Sample.java:7)
... 6 more

代 碼清單 5 給出的運(yùn)行結(jié)果可以看到,運(yùn)行時(shí)拋出了 java.lang.ClassCastException 異常。雖然兩個(gè)對(duì)象 obj1obj2 的類的名字相同,但是這兩個(gè)類是由不同的類加載器實(shí)例來(lái)加載的,因此不被 Java 虛擬機(jī)認(rèn)為是相同的。

了解了這一點(diǎn)之后,就可以理解代理模式的設(shè)計(jì)動(dòng)機(jī)了。代理模式是為了保證 Java 核心庫(kù)的類型安全。所有 Java 應(yīng)用都至少需要引用 java.lang.Object 類,也就是說(shuō)在運(yùn)行的時(shí)候,java.lang.Object 這個(gè)類需要被加載到 Java 虛擬機(jī)中。如果這個(gè)加載過(guò)程由 Java 應(yīng)用自己的類加載器來(lái)完成的話,很可能就存在多個(gè)版本的 java.lang.Object 類,而且這些類之間是不兼容的。通過(guò)代理模式,對(duì)于 Java 核心庫(kù)的類的加載工作由引導(dǎo)類加載器來(lái)統(tǒng)一完成,保證了 Java 應(yīng)用所使用的都是同一個(gè)版本的 Java 核心庫(kù)的類,是互相兼容的。

不同的類加載器為相同名稱的類創(chuàng)建了額外的名稱空間。相同名稱的類可以并存在 Java 虛擬機(jī)中,只需要用不同的類加載器來(lái)加載它們即可。不同類加載器加載的類之間是不兼容的,這就相當(dāng)于在 Java 虛擬機(jī)內(nèi)部創(chuàng)建了一個(gè)個(gè)相互隔離的 Java 類空間。這種技術(shù)在許多框架中都被用到,后面會(huì)詳細(xì)介紹。

下面具體介紹類加載器加載類的詳細(xì)過(guò)程。

加載類的過(guò)程

在前面介紹類加載器的代理模式的時(shí)候,提到過(guò)類加載器會(huì)首先代理給其它類加載器來(lái)嘗試加載某個(gè)類。這就意味著真正完成類的加載工作的類加載器 和啟動(dòng)這個(gè)加載過(guò)程的類加載器,有可能不是同一個(gè)。真正完成類的加載工作是通過(guò)調(diào)用 defineClass 來(lái)實(shí)現(xiàn)的;而啟動(dòng)類的加載過(guò)程是通過(guò)調(diào)用 loadClass 來(lái)實(shí)現(xiàn)的。前者稱為一個(gè)類的定義加載器(defining loader),后者稱為初始加載器(initiating loader)。在 Java 虛擬機(jī)判斷兩個(gè)類是否相同的時(shí)候,使用的是類的定義加載器。也就是說(shuō),哪個(gè)類加載器啟動(dòng)類的加載過(guò)程并不重要,重要的是最終定義這個(gè)類的加載器。兩種類加 載器的關(guān)聯(lián)之處在于:一個(gè)類的定義加載器是它引用的其它類的初始加載器。如類 com.example.Outer 引用了類 com.example.Inner,則由類 com.example.Outer 的定義加載器負(fù)責(zé)啟動(dòng)類 com.example.Inner 的加載過(guò)程。

方法 loadClass() 拋出的是 java.lang.ClassNotFoundException 異常;方法 defineClass() 拋出的是 java.lang.NoClassDefFoundError 異常。

類加載器在成功加載某個(gè)類之后,會(huì)把得到的 java.lang.Class 類的實(shí)例緩存起來(lái)。下次再請(qǐng)求加載該類的時(shí)候,類加載器會(huì)直接使用緩存的類的實(shí)例,而不會(huì)嘗試再次加載。也就是說(shuō),對(duì)于一個(gè)類加載器實(shí)例來(lái)說(shuō),相同全名的 類只加載一次,即 loadClass 方法不會(huì)被重復(fù)調(diào)用。

下面討論另外一種類加載器:線程上下文類加載器。

線程上下文類加載器

線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread 中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl) 用來(lái)獲取和設(shè)置線程的上下文類加載器。如果沒有通過(guò) setContextClassLoader(ClassLoader cl) 方法進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。Java 應(yīng)用運(yùn)行的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運(yùn)行的代碼可以通過(guò)此類加載器來(lái)加載類和資源。

前面提到的類加載器的代理模式并不能解決 Java 應(yīng)用開發(fā)中會(huì)遇到的類加載器的全部問(wèn)題。Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫(kù)來(lái)提供,如 JAXP 的 SPI 接口定義包含在 javax.xml.parsers 包中。這些 SPI 的實(shí)現(xiàn)代碼很可能是作為 Java 應(yīng)用所依賴的 jar 包被包含進(jìn)來(lái),可以通過(guò)類路徑(CLASSPATH)來(lái)找到,如實(shí)現(xiàn)了 JAXP SPI 的 Apache Xerces 所包含的 jar 包。SPI 接口中的代碼經(jīng)常需要加載具體的實(shí)現(xiàn)類。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory 類中的 newInstance() 方法用來(lái)生成一個(gè)新的 DocumentBuilderFactory 的實(shí)例。這里的實(shí)例的真正的類是繼承自 javax.xml.parsers.DocumentBuilderFactory, 由 SPI 的實(shí)現(xiàn)所提供的。如在 Apache Xerces 中,實(shí)現(xiàn)的類是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。 而問(wèn)題在于,SPI 的接口是 Java 核心庫(kù)的一部分,是由引導(dǎo)類加載器來(lái)加載的;SPI 實(shí)現(xiàn)的 Java 類一般是由系統(tǒng)類加載器來(lái)加載的。引導(dǎo)類加載器是無(wú)法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)樗患虞d Java 的核心庫(kù)。它也不能代理給系統(tǒng)類加載器,因?yàn)樗窍到y(tǒng)類加載器的祖先類加載器。也就是說(shuō),類加載器的代理模式無(wú)法解決這個(gè)問(wèn)題。

線程上下文類加載器正好解決了這個(gè)問(wèn)題。如果不做任何的設(shè)置,Java 應(yīng)用的線程的上下文類加載器默認(rèn)就是系統(tǒng)上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就可以成功的加載到 SPI 實(shí)現(xiàn)的類。線程上下文類加載器在很多 SPI 的實(shí)現(xiàn)中都會(huì)用到。

下面介紹另外一種加載類的方法:Class.forName

Class.forName

Class.forName 是一個(gè)靜態(tài)方法,同樣可以用來(lái)加載類。該方法有兩種形式:Class.forName(String name, boolean initialize, ClassLoader loader)Class.forName(String className)。第一種形式的參數(shù) name 表示的是類的全名;initialize 表示是否初始化類;loader 表示加載時(shí)使用的類加載器。第二種形式則相當(dāng)于設(shè)置了參數(shù) initialize 的值為 true,loader 的值為當(dāng)前類的類加載器。Class.forName 的一個(gè)很常見的用法是在加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)的時(shí)候。如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance() 用來(lái)加載 Apache Derby 數(shù)據(jù)庫(kù)的驅(qū)動(dòng)。

在介紹完類加載器相關(guān)的基本概念之后,下面介紹如何開發(fā)自己的類加載器。


開發(fā)自己的類加載器

雖然在絕大多數(shù)情況下,系統(tǒng)默認(rèn)提供的類加載器實(shí)現(xiàn)已經(jīng)可以滿足需求。但是在某些情況下,您還是需要為應(yīng)用開發(fā)出自己的類加載器。比如您的應(yīng) 用通過(guò)網(wǎng)絡(luò)來(lái)傳輸 Java 類的字節(jié)代碼,為了保證安全性,這些字節(jié)代碼經(jīng)過(guò)了加密處理。這個(gè)時(shí)候您就需要自己的類加載器來(lái)從某個(gè)網(wǎng)絡(luò)地址上讀取加密后的字節(jié)代碼,接著進(jìn)行解密和驗(yàn) 證,最后定義出要在 Java 虛擬機(jī)中運(yùn)行的類來(lái)。下面將通過(guò)兩個(gè)具體的實(shí)例來(lái)說(shuō)明類加載器的開發(fā)。

文件系統(tǒng)類加載器

第一個(gè)類加載器用來(lái)加載存儲(chǔ)在文件系統(tǒng)上的 Java 字節(jié)代碼。完整的實(shí)現(xiàn)如 代 碼清單 6 所示。


清單 6. 文件系統(tǒng)類加載器
public class FileSystemClassLoader extends ClassLoader { 

private String rootDir;

public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}

protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
else {
return defineClass(name, classData, 0, classData.length);
}
}

private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}

代 碼清單 6 所示,類 FileSystemClassLoader 繼承自類 java.lang.ClassLoader。 在 表 1 中列出的 java.lang.ClassLoader 類的常用方法中,一般來(lái)說(shuō),自己開發(fā)的類加載器只需要覆寫 findClass(String name) 方法即可。java.lang.ClassLoader 類的方法 loadClass() 封裝了前面提到的代理模式的實(shí)現(xiàn)。該方法會(huì)首先調(diào)用 findLoadedClass() 方法來(lái)檢查該類是否已經(jīng)被加載過(guò);如果沒有加載過(guò)的話,會(huì)調(diào)用父類加載器的 loadClass() 方法來(lái)嘗試加載該類;如果父類加載器無(wú)法加載該類的話,就調(diào)用 findClass() 方法來(lái)查找該類。因此,為了保證類加載器都正確實(shí)現(xiàn)代理模式,在開發(fā)自己的類加載器時(shí),最好不要覆寫 loadClass() 方法,而是覆寫 findClass() 方法。

FileSystemClassLoaderfindClass() 方法首先根據(jù)類的全名在硬盤上查找類的字節(jié)代碼文件(.class 文件),然后讀取該文件內(nèi)容,最后通過(guò) defineClass() 方法來(lái)把這些字節(jié)代碼轉(zhuǎn)換成 java.lang.Class 類的實(shí)例。

網(wǎng)絡(luò)類加載器

下面將通過(guò)一個(gè)網(wǎng)絡(luò)類加載器來(lái)說(shuō)明如何通過(guò)類加載器來(lái)實(shí)現(xiàn)組件的動(dòng)態(tài)更新。即基本的場(chǎng)景是:Java 字節(jié)代碼(.class)文件存放在服務(wù)器上,客戶端通過(guò)網(wǎng)絡(luò)的方式獲取字節(jié)代碼并執(zhí)行。當(dāng)有版本更新的時(shí)候,只需要替換掉服務(wù)器上保存的文件即可。通過(guò) 類加載器可以比較簡(jiǎn)單的實(shí)現(xiàn)這種需求。

NetworkClassLoader 負(fù)責(zé)通過(guò)網(wǎng)絡(luò)下載 Java 類字節(jié)代碼并定義出 Java 類。它的實(shí)現(xiàn)與 FileSystemClassLoader 類似。在通過(guò) NetworkClassLoader 加載了某個(gè)版本的類之后,一般有兩種做法來(lái)使用它。第一種做法是使用 Java 反射 API。另外一種做法是使用接口。需要注意的是,并不能直接在客戶端代碼中引用從服務(wù)器上下載的類,因?yàn)榭蛻舳舜a的類加載器找不到這些類。使用 Java 反射 API 可以直接調(diào)用 Java 類的方法。而使用接口的做法則是把接口的類放在客戶端中,從服務(wù)器上加載實(shí)現(xiàn)此接口的不同版本的類。在客戶端通過(guò)相同的接口來(lái)使用這些實(shí)現(xiàn)類。網(wǎng)絡(luò)類加載 器的具體代碼見 下 載。

在介紹完如何開發(fā)自己的類加載器之后,下面說(shuō)明類加載器和 Web 容器的關(guān)系。


類加載器與 Web 容器

對(duì)于運(yùn)行在 Java EE™ 容器中的 Web 應(yīng)用來(lái)說(shuō),類加載器的實(shí)現(xiàn)方式與一般的 Java 應(yīng)用有所不同。不同的 Web 容器的實(shí)現(xiàn)方式也會(huì)有所不同。以 Apache Tomcat 來(lái)說(shuō),每個(gè) Web 應(yīng)用都有一個(gè)對(duì)應(yīng)的類加載器實(shí)例。該類加載器也使用代理模式,所不同的是它是首先嘗試去加載某個(gè)類,如果找不到再代理給父類加載器。這與一般類加載器的順 序是相反的。這是 Java Servlet 規(guī)范中的推薦做法,其目的是使得 Web 應(yīng)用自己的類的優(yōu)先級(jí)高于 Web 容器提供的類。這種代理模式的一個(gè)例外是:Java 核心庫(kù)的類是不在查找范圍之內(nèi)的。這也是為了保證 Java 核心庫(kù)的類型安全。

絕大多數(shù)情況下,Web 應(yīng)用的開發(fā)人員不需要考慮與類加載器相關(guān)的細(xì)節(jié)。下面給出幾條簡(jiǎn)單的原則:

  • 每個(gè) Web 應(yīng)用自己的 Java 類文件和使用的庫(kù)的 jar 包,分別放在 WEB-INF/classesWEB-INF/lib 目錄下面。
  • 多個(gè)應(yīng)用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由所有 Web 應(yīng)用共享的目錄下面。
  • 當(dāng)出現(xiàn)找不到類的錯(cuò)誤時(shí),檢查當(dāng)前類的類加載器和當(dāng)前線程的上下文類加載器是否正確。

在介紹完類加載器與 Web 容器的關(guān)系之后,下面介紹它與 OSGi 的關(guān)系。


類加載器與 OSGi

OSGi™ 是 Java 上的動(dòng)態(tài)模塊系統(tǒng)。它為開發(fā)人員提供了面向服務(wù)和基于組件的運(yùn)行環(huán)境,并提供標(biāo)準(zhǔn)的方式用來(lái)管理軟件的生命周期。OSGi 已經(jīng)被實(shí)現(xiàn)和部署在很多產(chǎn)品上,在開源社區(qū)也得到了廣泛的支持。Eclipse 就是基于 OSGi 技術(shù)來(lái)構(gòu)建的。

OSGi 中的每個(gè)模塊(bundle)都包含 Java 包和類。模塊可以聲明它所依賴的需要導(dǎo)入(import)的其它模塊的 Java 包和類(通過(guò) Import-Package),也可以聲明導(dǎo)出(export)自己的包和類,供其它模塊使用(通過(guò) Export-Package)。 也就是說(shuō)需要能夠隱藏和共享一個(gè)模塊中的某些 Java 包和類。這是通過(guò) OSGi 特有的類加載器機(jī)制來(lái)實(shí)現(xiàn)的。OSGi 中的每個(gè)模塊都有對(duì)應(yīng)的一個(gè)類加載器。它負(fù)責(zé)加載模塊自己包含的 Java 包和類。當(dāng)它需要加載 Java 核心庫(kù)的類時(shí)(以 java 開頭的包和類),它會(huì)代理給父類加載器(通常是啟動(dòng)類加載器)來(lái)完成。當(dāng)它需要加載所導(dǎo)入的 Java 類時(shí),它會(huì)代理給導(dǎo)出此 Java 類的模塊來(lái)完成加載。模塊也可以顯式的聲明某些 Java 包和類,必須由父類加載器來(lái)加載。只需要設(shè)置系統(tǒng)屬性 org.osgi.framework.bootdelegation 的值即可。

假設(shè)有兩個(gè)模塊 bundleA 和 bundleB,它們都有自己對(duì)應(yīng)的類加載器 classLoaderA 和 classLoaderB。在 bundleA 中包含類 com.bundleA.Sample,并且該類被聲明為導(dǎo)出的, 也就是說(shuō)可以被其它模塊所使用的。bundleB 聲明了導(dǎo)入 bundleA 提供的類 com.bundleA.Sample, 并包含一個(gè)類 com.bundleB.NewSample 繼承自 com.bundleA.Sample。 在 bundleB 啟動(dòng)的時(shí)候,其類加載器 classLoaderB 需要加載類 com.bundleB.NewSample, 進(jìn)而需要加載類 com.bundleA.Sample。由于 bundleB 聲明了類 com.bundleA.Sample 是導(dǎo)入的,classLoaderB 把加載類 com.bundleA.Sample 的工作代理給導(dǎo)出該類的 bundleA 的類加載器 classLoaderA。classLoaderA 在其模塊內(nèi)部查找類 com.bundleA.Sample 并定義它,所得到的類 com.bundleA.Sample 實(shí)例就可以被所有聲明導(dǎo)入了此類的模塊使用。對(duì)于以 java 開頭的類,都是由父類加載器來(lái)加載的。如果聲明了系統(tǒng)屬性 org.osgi.framework.bootdelegation=com.example.core.*, 那么對(duì)于包 com.example.core 中的類,都是由父類加載器來(lái)完成的。

OSGi 模塊的這種類加載器結(jié)構(gòu),使得一個(gè)類的不同版本可以共存在 Java 虛擬機(jī)中,帶來(lái)了很大的靈活性。不過(guò)它的這種不同,也會(huì)給開發(fā)人員帶來(lái)一些麻煩,尤其當(dāng)模塊需要使用第三方提供的庫(kù)的時(shí)候。下面提供幾條比較好的建議:

  • 如果一個(gè)類庫(kù)只有一個(gè)模塊使用,把該類庫(kù)的 jar 包放在模塊中,在 Bundle-ClassPath 中指明即可。
  • 如果一個(gè)類庫(kù)被多個(gè)模塊共用,可以為這個(gè)類庫(kù)單獨(dú)的創(chuàng)建一個(gè)模塊,把其它模塊需要用到的 Java 包聲明為導(dǎo)出的。其它模塊聲明導(dǎo)入這些類。
  • 如果類庫(kù)提供了 SPI 接口,并且利用線程上下文類加載器來(lái)加載 SPI 實(shí)現(xiàn)的 Java 類,有可能會(huì)找不到 Java 類。如果出現(xiàn)了 NoClassDefFoundError 異常,首先檢查當(dāng)前線程的上下文類加載器是否正確。通過(guò) Thread.currentThread().getContextClassLoader() 就可以得到該類加載器。該類加載器應(yīng)該是該模塊對(duì)應(yīng)的類加載器。如果不是的話,可以首先通過(guò) class.getClassLoader() 來(lái)得到模塊對(duì)應(yīng)的類加載器,再通過(guò) Thread.currentThread().setContextClassLoader() 來(lái)設(shè)置當(dāng)前線程的上下文類加載器。

總結(jié)

類加載器是 Java 語(yǔ)言的一個(gè)創(chuàng)新。它使得動(dòng)態(tài)安裝和更新軟件組件成為可能。本文詳細(xì)介紹了類加載器的相關(guān)話題,包括基本概念、代理模式、線程上下文類加載器、與 Web 容器和 OSGi 的關(guān)系等。開發(fā)人員在遇到 ClassNotFoundExceptionNoClassDefFoundError 等異常的時(shí)候,應(yīng)該檢查拋出異常的類的類加載器和當(dāng)前線程的上下文類加載器,從中可以發(fā)現(xiàn)問(wèn)題的所在。在開發(fā)自己的類加載器的時(shí)候,需要注意與已有的類加 載器組織結(jié)構(gòu)的協(xié)調(diào)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    深夜福利欲求不满的人妻| 九九热精品视频免费观看| 午夜午夜精品一区二区| 成人精品一区二区三区在线| 91播色在线免费播放| 色婷婷久久五月中文字幕| 欧美日韩精品人妻二区三区| 欧美野外在线刺激在线观看 | 日本黄色美女日本黄色| 99久久精品一区二区国产| 日韩精品免费一区二区三区| 伊人色综合久久伊人婷婷| 国产成人精品午夜福利| 中文字幕乱子论一区二区三区 | 好吊一区二区三区在线看| 欧美一区二区三区在线播放| 日韩精品一级片免费看| 四十女人口红哪个色好看| 日韩中文字幕欧美亚洲| 日韩精品在线观看完整版| 欧美日韩中黄片免费看| 亚洲熟女诱惑一区二区| 日韩性生活视频免费在线观看| 国产又粗又黄又爽又硬的| 翘臀少妇成人一区二区| 精品久久少妇激情视频| 日本特黄特色大片免费观看| 亚洲视频一区自拍偷拍另类| 亚洲中文字幕高清视频在线观看| 熟女高潮一区二区三区| 91蜜臀精品一区二区三区| 国产成人在线一区二区三区 | 日本理论片午夜在线观看| 福利在线午夜绝顶三级| 国产精品欧美激情在线播放| 黄片免费播放一区二区| 成人午夜视频在线播放| 欧美一区日韩一区日韩一区| 亚洲日本中文字幕视频在线观看| 激情三级在线观看视频| 国产成人午夜福利片片|