自定義系統類加載器
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);
}