java類加載器基本概念
類加載器是用來把java類加載到虛擬機中的胰柑。一般來說java虛擬機使用java類的步驟如下:.java文件經(jīng)過編譯器編譯后生成.class文件让腹。類加載器負責(zé)讀取這些字節(jié)碼文件捣鲸,并轉(zhuǎn)化成一個java.lang.Class類的實例。每一個這樣的實例都表示一個java類剥悟。通過這個實例的newInstance()就可以創(chuàng)建出該類的一個對象。
基本上所有的類加載器都是java.lang.ClassLoader的一個實例。
java.lang.ClassLoader介紹
方法 | 說明 |
---|---|
getParent() | 返回該類加載器的父類加載器冕屯。 |
loadClass(String name) | 加載名稱為 name 的類桐早,返回的結(jié)果是 java.lang.Class 類的實例。 |
findClass(String name) | 查找名稱為 name 的類栈戳,返回的結(jié)果是 java.lang.Class 類的實例岂傲。 |
findLoadedClass(String name) | 查找名稱為 name 的已經(jīng)被加載過的類,返回的結(jié)果是 java.lang.Class 類的實例子檀。 |
defineClass(String name, byte[] b, int off, int len) | 把字節(jié)數(shù)組 b 中的內(nèi)容轉(zhuǎn)換成 Java 類镊掖,返回的結(jié)果是 java.lang.Class 類的實例。這個方法被聲明為 final 的褂痰。 |
resolveClass(Class<?> c) | 鏈接指定的 Java 類亩进。 |
java中的類加載器大致分為兩類:一類是系統(tǒng)提供的,另一類是java應(yīng)用開發(fā)人員編寫的脐恩。
系統(tǒng)提供的類加載器有三個:
- 引導(dǎo)類加載器(bootstrap class loader):它用來加載 Java 的核心庫镐侯,是用原生代碼來實現(xiàn)的,并不繼承自 java.lang.ClassLoader驶冒。
- 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫苟翻。Java 虛擬機的實現(xiàn)會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類骗污。
- 系統(tǒng)類加載器(system class loader):它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH)來加載 Java 類崇猫。一般來說,Java 應(yīng)用的類都是由它來完成加載的需忿∽缏可以通過 ClassLoader.getSystemClassLoader()來獲取它蜡歹。
除了引導(dǎo)類加載器,其他的類加載器都必須有一個父類加載器涕烧≡露可以通過getParent()來獲取父類加載器。系統(tǒng)類加載器的父類加載器是擴展類加載器议纯,而擴展類加載器的父類加載器是引導(dǎo)類加載器父款;對于開發(fā)人員編寫的類加載器來說,其父類加載器是加載此類加載器 Java 類的類加載器瞻凤。
除了系統(tǒng)類加載器外憨攒,開發(fā)人員可以通過繼承java.lang.ClassLoader來實現(xiàn)自己的類加載器,以滿足一些特殊的需求阀参。
每個java類都維護類一個指向定義它(調(diào)用defineClass來創(chuàng)建該類實例的classloader)的類加載器的引用肝集,可以通過getClassLoader()來獲取。
類加載器的代理模式
類加載器在嘗試查找一個類的字節(jié)碼文件并定義它時蛛壳,后先代理給父類加載器杏瞻,有父類加載器先去嘗試加載這個類,以此類推衙荐。在介紹這個代理模式之前伐憾,先說明一下java虛擬機時如何判定兩個java類是否相同的。jvm不僅要判斷兩類的類名是否相同赫模,還要比較加載這兩個類的加載器是否一樣。只有當(dāng)兩種都相同時蒸矛,才認(rèn)為兩個類時相同的瀑罗。即便是相同的字節(jié)碼,被不同的類加載器加載生成后所得到的類雏掠,也是不同的斩祭。下面來看個例子:
自定義的類加載器
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";
}
}
Sample類
package com.example;
public class Sample {
private Sample instance;
public void setSample(Object instance) {
this.instance = (Sample) instance;
}
}
測試方法
public void testClassIdentity() {
String classDataRootPath = "";//字節(jié)碼文件路徑
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();
}
}
運行程序會報java.lang.ClassCastException,說明這個兩個類不相同乡话。
了解了這一點之后摧玫,就可以理解代理模式的設(shè)計動機了。代理模式是為了保證 Java 核心庫的類型安全绑青。所有 Java 應(yīng)用都至少需要引用 java.lang.Object類诬像,也就是說在運行的時候,java.lang.Object這個類需要被加載到 Java 虛擬機中闸婴。如果這個加載過程由 Java 應(yīng)用自己的類加載器來完成的話坏挠,很可能就存在多個版本的 java.lang.Object類,而且這些類之間是不兼容的邪乍。通過代理模式降狠,對于 Java 核心庫的類的加載工作由引導(dǎo)類加載器來統(tǒng)一完成对竣,保證了 Java 應(yīng)用所使用的都是同一個版本的 Java 核心庫的類,是互相兼容的榜配。
不同的類加載器為相同名稱的類創(chuàng)建了額外的名稱空間否纬。相同名稱的類可以并存在 Java 虛擬機中,只需要用不同的類加載器來加載它們即可蛋褥。不同類加載器加載的類之間是不兼容的临燃,這就相當(dāng)于在 Java 虛擬機內(nèi)部創(chuàng)建了一個個相互隔離的 Java 類空間。
加載類的過程
在前面介紹類加載器的代理模式的時候壁拉,提到過類加載器會首先代理給其它類加載器來嘗試加載某個類谬俄。這就意味著真正完成類的加載工作的類加載器和啟動這個加載過程的類加載器,有可能不是同一個弃理。真正完成類的加載工作是通過調(diào)用 defineClass來實現(xiàn)的溃论;而啟動類的加載過程是通過調(diào)用 loadClass來實現(xiàn)的。前者稱為一個類的定義加載器(defining loader)痘昌,后者稱為初始加載器(initiating loader)钥勋。在 Java 虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器辆苔。也就是說算灸,哪個類加載器啟動類的加載過程并不重要,重要的是最終定義這個類的加載器驻啤。兩種類加載器的關(guān)聯(lián)之處在于:一個類的定義加載器是它引用的其它類的初始加載器菲驴。如類 com.example.Outer引用了類 com.example.Inner,則由類 com.example.Outer的定義加載器負責(zé)啟動類 com.example.Inner的加載過程骑冗。
方法 loadClass()拋出的是 java.lang.ClassNotFoundException異常赊瞬;方法 defineClass()拋出的是 java.lang.NoClassDefFoundError異常。
類加載器在成功加載某個類之后贼涩,會把得到的 java.lang.Class類的實例緩存起來巧涧。下次再請求加載該類的時候,類加載器會直接使用緩存的類的實例遥倦,而不會嘗試再次加載谤绳。也就是說,對于一個類加載器實例來說袒哥,相同全名的類只加載一次缩筛,即 loadClass方法不會被重復(fù)調(diào)用。