1. 程式人生 > >類載入器 - 自定義系統類載入器及執行緒上下文類載入器

類載入器 - 自定義系統類載入器及執行緒上下文類載入器

自定義系統類載入器

ClassLoader.getSystemClassLoader()方法詳解

方法說明

返回用於委託的系統類載入器,它是新建ClassLoader例項的預設的委託雙親,通常也是啟動應用的類載入器。

這個方法在執行啟動期間很早的時候就被呼叫,在呼叫時首先會建立系統載入器,而且會將其設定為呼叫該執行緒的上下文類載入器。

預設的系統類載入器是與這個類的實現相關的一個例項。

如果系統屬性java.system.class.loader被定義了,這個屬性的值就將做為返回的類載入器的名字。這個類是使用預設的系統類載入器所載入的,且必須要定義一個預設的引數為ClassLoader的構造方法,所生成的類載入器就被定義為新的系統類載入器。

如果安全管理器存在,並且呼叫者的類載入器是不是null和呼叫者的類載入器是不一樣的,或者系統類載入器的祖先,則此方法呼叫安全管理器的checkPermission方法與RuntimePermission("getClassLoader")許可權驗證訪問到系統類載入器。 如果沒有, SecurityException將被丟擲。

原始碼解析

// 系統類載入器
// @GuardedBy("ClassLoader.class")
private static ClassLoader scl;

// 設定系統類載入器後,將其設定為true
// @GuardedBy("ClassLoader.class")
private static boolean sclSet;

/**
 * getSystemClassLoader方法原始碼
 */
@CallerSensitive
public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}

// 初始化系統類載入器
private static synchronized void initSystemClassLoader() {
    // 如果系統類載入器沒有被設定
    if (!sclSet) {
        // 如果系統類載入器已經被設定,不合理,丟擲異常
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        // Launcher為系統類載入器和拓展類載入器的一個包裝,程式碼並不開源
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        if (l != null) {
            Throwable oops = null;
            // 將Launcher預設的系統類載入器賦給了scl
            scl = l.getClassLoader();
            try {
                // 獲取到系統類載入器,可能是系統預設的AppClassLOader,有可能是使用者自定義的系統類載入器
                scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
            } catch (PrivilegedActionException pae) {
                // 異常處理
                oops = pae.getCause();
                if (oops instanceof InvocationTargetException) {
                    oops = oops.getCause();
                }
            }
            if (oops != null) {
                if (oops instanceof Error) {
                    throw (Error) oops;
                } else {
                    // wrap the exception
                    throw new Error(oops);
                }
            }
        }
        // 系統類載入器已經被設定
        sclSet = true;
    }
}

class SystemClassLoaderAction
    implements PrivilegedExceptionAction<ClassLoader> {
    private ClassLoader parent;

    SystemClassLoaderAction(ClassLoader parent) {
        this.parent = parent;
    }

    public ClassLoader run() throws Exception {
        // 獲取系統屬性
        String cls = System.getProperty("java.system.class.loader");
        // 如果系統屬性為空,系統類載入器就是預設的AppClassLoader
        if (cls == null) {
            return parent;
        }

        //如果系統屬性不為空,獲取cls所對應的class的構造方法物件
        Constructor<?> ctor = Class.forName(cls, true, parent)
            .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
        // 建立例項
        ClassLoader sys = (ClassLoader) ctor.newInstance(
            new Object[] { parent });
        // 將sys設定為當前執行緒的上下文類載入器
        Thread.currentThread().setContextClassLoader(sys);
        return sys;
    }
}

自定義系統類載入器

首先,沿用前文中的自定義類載入器ClassLoaderTest,新增一個構造方法:

public ClassLoaderTest(ClassLoader classLoader) {
       super(classLoader);
 }

PS:為什麼要新增這樣一個構造方法:

​ 在上文的程式碼解析中其實給出了答案,在設定了自定義系統類載入器,通過反射的方法獲取自定義系統類載入器的Constructor物件時,需要呼叫到該構造方法。

  Constructor<?> ctor = Class.forName(cls, true, parent)
            .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });

然後,編寫一個測試類

public class Test21 {
    public static void main(String[] args) {
        System.out.println(System.getProperty("java.system.class.loader"));
    }
}

編譯該類,通過java指令設定java.system.class.loader的值執行該類

java -Djava.system.class.loader=classloader.ClassLoaderTest  classloader.Test21

執行結果如下

執行緒上下文類類載入器

當前類載入器

當前類載入器就是載入當前類的類載入器,每個類都會使用自己的類載入器(即載入自身的類載入器)來去載入其他類(指的是所依賴的類),如果Class A引用Class B,那麼Class A的類載入器就會去載入Class B(前提是Class B尚未被載入)。

執行緒上下文類載入器

執行緒上下文類載入器的概念

執行緒上下文類載入器就是當前執行緒的Current ClassLoader。
JDK1.2開始引入,類Thread中的getContextClassLoader()與setContextClassLoader(ClassLoader cl)分別用來獲取和設定上下文類載入器。如果沒有通過setContextClassLoader(ClassLoader cl)進行設定,執行緒將繼承其父執行緒的上下文類載入器。
Java應用執行時的初始執行緒的上下文類載入器是系統類載入器,線上程中執行的程式碼可以通過該類載入器來載入類與資源。

執行緒上下文類載入器的重要性

父類載入器可以使用當前執行緒Thread.currentThread().getContextClassLoader()所指定的類載入器所載入的類。
這就改變了父類載入器不能使用子類載入器或是其他沒有直接父子關係的類載入器所載入的類的情況,即改變了雙親委託模型。
在雙親委託模型下,類載入是由下至上的,即下層的類載入器會委託上層進行載入。但是對於SPI(Service Provider Interface)來說,有些介面是Java核心庫所提供的,而Java核心庫是由啟動類載入器來載入的,而這些介面的實現卻來自與不同的jar包(廠商提供),Java的啟動類載入器是不會載入其他來源的jar包,這樣傳統的雙親委託模型無法滿足SPI的需求。通過給當前執行緒設定上下文類載入器,就可以由設定的上下文類載入器來實現對於介面實現類的載入。
當高層提供了統一的介面讓低層去實現,同時又要在高層載入(或例項化)低層的類時,就必須要通過執行緒上下文類載入來幫助高層的ClassLoader找到並載入該類。

執行緒上下文類載入器的使用

執行緒上下文類載入器的一般使用模式(獲取 - 使用 - 還原)

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(customizeClassLoader);
} finally {
    Thread.currentThread().setContextClassLoader(classLoader);
}