Java類加載器之ContextClassLoader詳解
ContextClassLoader
ContextClassLoader是一種與線程相關(guān)的類加載器,類似ThreadLocal,每個(gè)線程對應(yīng)一個(gè)上下文類加載器.在實(shí)際使用時(shí)一般都用下面的經(jīng)典結(jié)構(gòu):
ClassLoader targetClassLoader = null;// 外部參數(shù) ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(targetClassLoader); // TODO } catch (Exception e) { e.printStackTrace(); } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); }
- 首先獲取當(dāng)前線程的線程上下文類加載器并保存到方法棧,然后將外部傳遞的類加載器設(shè)置為當(dāng)前線程上下文類加載器
- doSomething則可以利用新設(shè)置的類加載器做一些事情
- 最后在設(shè)置當(dāng)前線程上下文類加載器為老的類加載器
上面的使用場景是什么?Java默認(rèn)的類加載機(jī)制是委托機(jī)制,但是有些時(shí)候需要破壞這種固定的機(jī)制
具體來說,比如Java中的SPI(Service Provider Interface)是面向接口編程的,服務(wù)規(guī)則提供者會在JRE的核心API里面提供服務(wù)訪問接口,而具體的實(shí)現(xiàn)則由其他開發(fā)商提供.我們知道Java核心API,比如rt.jar包,是使用Bootstrap ClassLoader加載的,而用戶提供的jar包再有AppClassLoader加載.并且我們知道一個(gè)類由類加載器A加載,那么這個(gè)類依賴類也應(yīng)該由相同的類加載器加載.那么Bootstrap ClassLoader加載了服務(wù)提供者在rt.jar里面提供的搜索開發(fā)上提供的實(shí)現(xiàn)類的API類(ServiceLoader),那么這些API類里面依賴的類應(yīng)該也是有Bootstrap ClassLoader來加載.而上面說了用戶提供的Jar包有AppClassLoader加載,所以需要一種違反雙親委派模型的方法,線程上下文類加載器ContextClassLoader就是為了解決這個(gè)問題.
下面使用JDBC來具體說明,JDBC是基于SPI機(jī)制來發(fā)現(xiàn)驅(qū)動提供商提供的實(shí)現(xiàn)類,提供者只需在JDBC實(shí)現(xiàn)的jar的META-INF/services/java.sql.Driver文件里指定實(shí)現(xiàn)類的方式暴露驅(qū)動提供者.例如:MYSQL實(shí)現(xiàn)的jar如下:
其中MYSQL的驅(qū)動如下實(shí)現(xiàn)了java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver
引入MySQL驅(qū)動的jar包,測試類如下:
import java.sql.Driver; import java.util.Iterator; import java.util.ServiceLoader; public class MySQLClassLoader { public static void main(String[] args) { ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); Iterator<Driver> iterator = loader.iterator(); while (iterator.hasNext()) { Driver driver = (Driver) iterator.next(); System.out.println("driver:" + driver.getClass() + ",loader:" + driver.getClass().getClassLoader()); } System.out.println("current thread contxtloader:" + Thread.currentThread().getContextClassLoader()); System.out.println("ServiceLoader loader:" + ServiceLoader.class.getClassLoader()); } }
執(zhí)行結(jié)果如下:
driver:class com.mysql.jdbc.Driver,loader:sun.misc.Launcher$AppClassLoader@2a139a55
driver:class com.mysql.fabric.jdbc.FabricMySQLDriver,loader:sun.misc.Launcher$AppClassLoader@2a139a55
driver:class com.alibaba.druid.proxy.DruidDriver,loader:sun.misc.Launcher$AppClassLoader@2a139a55
driver:class com.alibaba.druid.mock.MockDriver,loader:sun.misc.Launcher$AppClassLoader@2a139a55
current thread contxtloader:sun.misc.Launcher$AppClassLoader@2a139a55
ServiceLoader loader:null
從執(zhí)行結(jié)果中可以知道ServiceLoader的加載器為Bootstrap,因?yàn)檫@里輸出了null,并且從該類在rt.jar里面,也可以證明.
當(dāng)前線程上下文類加載器為AppClassLoader.而com.mysql.jdbc.Driver則使用AppClassLoader加載.我們知道如果一個(gè)類中引用了另外一個(gè)類,那么被引用的類也應(yīng)該由引用方類加載器來加載,而現(xiàn)在則是引用方ServiceLoader使用BootStartClassLoader加載,被引用方則使用子加載器APPClassLoader來加載了.是不是很詭異.
下面來看一下ServiceLoader的load方法源碼:
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); } public static <S> ServiceLoader<S> load(Class<S> service) { // 獲取當(dāng)前線程上下文加載器,這里是APPClassLoader ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
上述代碼獲得了線程上下文加載器(其實(shí)就是AppClassLoader),并將該類加載器傳遞到下面的ServiceLoader類的構(gòu)造方法loader成員變量中:
private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
上述loader變量什么時(shí)候使用的?看一下下面的代碼:
public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } }
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { // 使用loader類加載器加載 // 至于cn怎么來的,可以參照next()方法 c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
到目前為止:ContextClassLoader的作用都是為了破壞Java類加載委托機(jī)制,JDBC規(guī)范定義了一個(gè)JDBC接口,然后使用SPI機(jī)制提供的一個(gè)叫做ServiceLoader的Java核心API(rt.jar里面提供)用來掃描服務(wù)實(shí)現(xiàn)類,服務(wù)實(shí)現(xiàn)者提供的jar,比如MySQL驅(qū)動則是放到我們的classpath下面.從上文知道默認(rèn)線程上下文類加載器就是AppClassLoader,所以例子里面沒有顯示在調(diào)用ServiceLoader前設(shè)置線程上下文類加載器為AppClassLoader,ServiceLoader內(nèi)部則獲取當(dāng)前線程上下文類加載器(這里為AppClassLoader)來加載服務(wù)實(shí)現(xiàn)者的類,這里加載了classpath下的MySQL的驅(qū)動實(shí)現(xiàn).
可以嘗試在調(diào)用ServiceLoader的load方法前設(shè)置線程上下文類加載器為ExtClassLoader,代碼如下:
Thread.currentThread().setContextClassLoader(ContextClassLoaderTest.class.getClassLoader().getParent());
然后運(yùn)行本例子,設(shè)置后ServiceLoader內(nèi)部則獲取當(dāng)前線程上下文類加載器為ExtClassLoader,然后會嘗試使用ExtClassLoader去查找JDBC驅(qū)動實(shí)現(xiàn),而ExtClassLoader掃描類的路徑為:JAVA_HOME/jre/lib/ext/,而這下面沒有驅(qū)動實(shí)現(xiàn)的Jar,所以不會查找到驅(qū)動.
總結(jié)下,當(dāng)父類加載器需要加載子類加載器中的資源時(shí),可以通過設(shè)置和獲取線程上下文類加載器來實(shí)現(xiàn).
到此這篇關(guān)于Java類加載器之ContextClassLoader詳解的文章就介紹到這了,更多相關(guān)ContextClassLoader詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java利用StringBuffer替換特殊字符的方法實(shí)現(xiàn)
這篇文章主要介紹了Java利用StringBuffer替換特殊字符的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04詳解Java如何實(shí)現(xiàn)在PDF中插入,替換或刪除圖像
圖文并茂的內(nèi)容往往讓人看起來更加舒服,如果只是文字內(nèi)容的累加,往往會使讀者產(chǎn)生視覺疲勞。搭配精美的文章配圖則會使文章內(nèi)容更加豐富。那我們要如何在PDF中插入、替換或刪除圖像呢?別擔(dān)心,今天為大家介紹一種高效便捷的方法2023-01-01Java中的Valid和Validated的比較內(nèi)容
在本篇文章里小編給大家整理的是關(guān)于Java中的Valid和Validated的比較內(nèi)容,對此有興趣的朋友們可以學(xué)習(xí)參考下。2021-02-02JVM解密之解構(gòu)類加載與GC垃圾回收機(jī)制詳解
本文主要介紹了Java虛擬機(jī)(JVM)的內(nèi)存劃分、類加載機(jī)制以及垃圾回收機(jī)制,JVM的內(nèi)存劃分為程序計(jì)數(shù)器、棧、堆和方法區(qū),類加載機(jī)制包括類加載過程、加載器模型和雙親委派模型,垃圾回收機(jī)制主要包括標(biāo)記-清除、標(biāo)記-復(fù)制、標(biāo)記-整理和分代回收算法2025-02-02SpringBoot集成MyBatis的分頁插件PageHelper實(shí)例代碼
這篇文章主要介紹了SpringBoot集成MyBatis的分頁插件PageHelper的相關(guān)操作,需要的朋友可以參考下2017-08-08