Java 類載入器(ClassLoader)/雙親委派模型
ClassLoader類載入器
Class類描述的是整個類的資訊,在Class類中提供的forName()方法,這個方法根據ClassPath配置的路徑進行類的載入,如果說現在你的類的載入路徑可能是網路、檔案,這個時候就必須實現類載入器,也就是ClassLoader類的主要作用。
認識ClassLoader
首先通過Class類觀察如下方法:
//自定義類,這個類一定在CLASSPATH中
class Member{}
public class Test {
public static void main(String[] args) {
Class<?> cls = Member.class ;
System.out.println(cls.getClassLoader()) ;
System.out.println(cls.getClassLoader().getParent()) ;
System.out.println(cls.getClassLoader().getParent().getParent());
}
}
執行結果
此時出現了兩個類載入器:ExtClassLoader(擴充套件類載入器)、AppClassLoader(應用程式類載入器)。
那麼,什麼是類載入器?
Bootstrap(啟動類載入器):這個類載入器使用C++實現,是虛擬機器自身的一部分;其他的類載入器都由Java語言實現,獨立於JVM外部並且都繼承於java.lang.ClassLoader.BootStrap類載入器負責將存放於\lib目錄中(或者被-Xbootclasspath引數指定路徑中)能被虛擬機器識別的(僅按照檔名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被載入)類庫載入到JVM記憶體中。啟動類載入器無法被Java程式直接引用。
ExtClassLoader(擴充套件類載入器):它負責載入\lib\ext目錄中,或者被java.ext.dirs系統變數指定的路徑中的類庫。開發者可以直接使用擴充套件類載入器。
AppClassLoader(應用程式類載入器):負責載入使用者類路徑(ClassPath)上指定的類庫,如果應用程式中沒有自定義自己的類載入器,則此載入器就是程式中預設的類載入器。
雙親委派模型
我們的應用程式都是由這三種載入器互相配合進行載入的,如果有必要,還可以加入自定義的類載入器。這些類載入器的關係一般如下圖所示:
上圖展示的類載入器之間的這種層次關係,就稱為類載入器的雙親委派模型。雙親委派模型要求除了頂層的父類載入器外,其餘的類載入器都應有自己的父類載入器。
雙親委派模型的工作流程是:如果一個類載入器收到了類載入請求,它首先不會自己去嘗試載入這個類,而是把這個請求委託給父類載入器去完成,每一個層次的類載入器都是如此。因此,所有的載入請求都應當傳送到頂層的BootStrap載入器中,只有當父載入器反饋無法完成這個載入請求時(在自己搜尋範圍中沒有找到此類),子載入器才會嘗試自己去載入。
類載入器的雙親委派模型從JDK1.2引入後被廣泛應用於之後幾乎所有的Java程式中,但它並不是強制性約束,甚至可以破壞雙親委派模型來進行類載入,最典型的就是OSGI技術。
例:觀察CLassLoader.loadClass()方法
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
* 自定義類載入器*
自定義類載入器:使用者決定類從哪裡載入。
ClassLoader類中提供有如下方法(進行類的載入):
ClassLoader類中提供有如下方法(進行類的載入):
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
例:觀察預設類載入器
// 自定義類,這個類一定在CLASSPATH中
class Member{
@Override
public String toString() {
return "Member";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception{
System.out.println(Class.forName("Member").getClassLoader().loadClass("Member").newInstance());
}
}
例:在Desktop上建立Member.java檔案
// 自定義類,這個類一定在CLASSPATH中
class Member{
@Override
public String toString() {
return "Member";
}
}
隨後將此檔案用javac編譯後生成class檔案。現在希望通過自定義的類載入器實現/Desktop/Member.class檔案的載入。
ClassLoader提供的類載入:
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
// 自定義類載入器
class MyClassLoader extends ClassLoader {
/**
* 實現一個自定義的類載入器,傳入類名稱,通過指定路徑載入
* @param className 類名稱
* @return 返回的Class物件
* @throws Exception
*/
public Class<?> loadData(String className) throws Exception {
// 載入類檔案的資訊
byte[] classData = this.loadClassData() ;
return super.defineClass(className,classData,0,classData.length) ;
}
/**
* 通過指定的檔案路徑進行類的檔案載入,實際上就是進行二進位制檔案讀取
* @return 類檔案資料
* @throws Exception
*/
private byte[] loadClassData() throws Exception {
InputStream input = new FileInputStream("/Users/yuisama/Desktop/Member.class") ;
// 取得所有位元組內容,放到記憶體中
ByteArrayOutputStream bos = new ByteArrayOutputStream() ;
// 讀取緩衝區
byte[] data = new byte[20] ;
int temp = 0 ;
while ((temp = input.read(data))!=-1){
bos.write(data,0,temp) ;
}
byte[] result = bos.toByteArray() ;
input.close() ;
bos.close() ;
return result ;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception{
Class<?> cls = new MyClassLoader().loadData("Member") ;
System.out.println(cls.getClassLoader()) ;
System.out.println(cls.getClassLoader().getParent()) ;
System.out.println(cls.getClassLoader().getParent().getParent()) ;
System.out.println(cls.newInstance());
}
}
類載入器給使用者提供最大的幫助為:可以通過動態的路徑進行類的載入操作
比較兩個類相等的前提:必須是由同一個類載入器載入的前提下才有意義。否則,即使兩個類來源於同一個Class檔案,被同一個虛擬機器載入,只要載入他們的類載入器不同,那麼這兩個類註定不想等。