Java中的上下文加載器ContextClassLoader詳解
ContextClassLoader
ContextClassLoader是通過 Thread.currentThread().getContextClassLoader() 返回該線程上下文的ClassLoader
1、前置知識
在講解ContextClassLoader之前,需要先提兩個知識點:
1)雙親委派模型
- 啟動類加載器(Bootstrap ClassLoader):負責將放在<JAVA HOME>\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是虛擬機識別的類庫加載到虛擬機內(nèi)存中。啟動類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時,如果需要把加載請求委派給引導類加載器,那直接使用null代替即可
- 擴展類加載器(ExtClassLoader):由sun.misc.Launcher$ExtClassLoader實現(xiàn),它負責加載<JAVA HOME>\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫,開發(fā)者可以直接使用擴展類加載器
- 應用程序類加載器(AppClassLoader):由sun.misc.Launcher$AppClassLoader實現(xiàn)。由于這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統(tǒng)類加載。它負責加載用戶類路徑(ClassPath)上所有指定的類庫,開發(fā)者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器
類加載之間的這種層次關系,稱為類加載器的雙親委派模型。雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應當有自己的父類加載器。這里類加載器之間的父子關系一般不會以繼承的關系來實現(xiàn),而是都使用組合關系來復用父加載器的代碼
雙親委派模型的工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器,只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載
使用雙親委派模型來組織類加載器之間的關系,有一個顯而易見的好處就是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關系。例如類 java.lang.Object ,它存放在 rt.jar 之中,無論哪一個類加載器要加載這個類,最終都是委派給處于模型最頂端的啟動類加載器進行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個類
2)如果一個類由類加載器A加載,那么這個類的依賴類也是由相同的類加載器加載
比如Spring作為一個Bean工廠,它需要創(chuàng)建業(yè)務類的實例,并且在創(chuàng)建業(yè)務類實例之前需要加載這些類。Spring是通過調用 Class.forName 來加載業(yè)務類的。調用 Class.forName() 的時候,會獲取調用該方法的類的類加載器,使用該類加載器來加載 Class.forName() 中傳入的類,代碼如下:
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement { @CallerSensitive public static Class<?> forName(String className) throws ClassNotFoundException { // 獲取調用該方法的類 Class<?> caller = Reflection.getCallerClass(); // ClassLoader.getClassLoader獲取調用該方法的類的類加載器 return forName0(className, true, ClassLoader.getClassLoader(caller), caller); }
2、為什么需要ContextClassLoader?
當我們需要加載一個類,從自定義ClassLoader,到AppClassLoader,再到ExtClassLoader,最后到Bootstrap ClassLoader。沒問題, 很順利。這是從下到上加載。但是反過來,當從上到下加載的時候,這個變得是一個不可能完成的任務。為了彌補這個缺陷, 特定設計的ContextClassLoader
這里你可能會有個疑問:為什么會出現(xiàn)從上到下加載的情況。比如一個類是由Bootstrap ClassLoader加載,該類引用了一個我們自己開發(fā)的類(該類能被AppClassLoader加載但不能被Bootstrap ClassLoader加載),由如果一個類由類加載器A加載,那么這個類的依賴類也是由相同的類加載器加載可知:默認情況下我們自己開發(fā)的類會被Bootstrap ClassLoader嘗試加載,最終會由于無法加載到類而拋出異常
以SPI為例,SPI接口屬于Java核心庫,由BootstrapClassLoader加載,當SPI接口想要引用第三方實現(xiàn)類的具體方法時,BootstrapClassLoader無法加載Classpath下的第三方實現(xiàn)類,這時就需要使用線程上下文類加載器ContextClassLoader來解決。借助這種機制可以打破雙親委托機制限制
SPI核心類ServiceLoader源碼如下:
public final class ServiceLoader<S> implements Iterable<S> { public static <S> ServiceLoader<S> load(Class<S> service) { // 線程上下文類加載器,在Launcher類的構造器中被賦值為AppClassLoader,它可以讀到ClassPath下的自定義類 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
3、ContextClassLoader默認為AppClassLoader
JVM啟動時,會去調用Launcher類的構造方法:
public class Launcher { public Launcher() { ClassLoader extcl; try { // 首先創(chuàng)建擴展類加載器 extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader"); } // Now create the class loader to use to launch the application try { // 再創(chuàng)建AppClassLoader并把extcl作為父加載器傳遞給AppClassLoader loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader"); } // 設置線程上下文類加載器,稍后分析 Thread.currentThread().setContextClassLoader(loader); // 省略其他代碼... }
Launcher初始化時首先會創(chuàng)建ExtClassLoader類加載器,然后再創(chuàng)建AppClassLoader并把ExtClassLoader傳遞給它作為父類加載器,還把AppClassLoader默認設置為線程上下文類加載器
4、子線程ContextClassLoader默認為父線程的ContextClassLoader
Thread在 init()
方法中會把子線程ContextClassLoader設置為父線程的ContextClassLoader
public class Thread implements Runnable { private ClassLoader contextClassLoader; private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // 省略其他代碼... // 當前線程為父線程 Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); // 子線程ContextClassLoader設置為父線程的ContextClassLoader if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; // 省略其他代碼... }
到此這篇關于Java中的上下文加載器ContextClassLoader詳解的文章就介紹到這了,更多相關ContextClassLoader詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
@FeignClient?path屬性路徑前綴帶路徑變量時報錯的解決
這篇文章主要介紹了@FeignClient?path屬性路徑前綴帶路徑變量時報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07springboot整合netty框架實現(xiàn)站內(nèi)信
Netty 是一個基于NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發(fā)出一個網(wǎng)絡應用,這篇文章主要介紹了springboot整合netty框架的方式小結,需要的朋友可以參考下2022-12-12SpringBoot啟動嵌入式Tomcat的實現(xiàn)步驟
本文主要介紹了淺談SpringBoot如何啟動嵌入式Tomcat,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08深扒Java中POJO、VO、DO、DTO、PO、BO、AO、DAO的概念和區(qū)別以及如何應用
po vo bo dto dao 和 pojo 是軟件開發(fā)中經(jīng)常使用的一些概念,用于設計和實現(xiàn)對象模型,下面將分別解釋這些概念的含義及其在開發(fā)中的應用,這篇文章主要給大家介紹了關于Java中POJO、VO、DO、DTO、PO、BO、AO、DAO的概念和區(qū)別以及如何應用的相關資料,需要的朋友可以參考下2024-08-08