1. 程式人生 > >Java 類載入器(ClassLoader)/雙親委派模型

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檔案,被同一個虛擬機器載入,只要載入他們的類載入器不同,那麼這兩個類註定不想等。