Java類加載器之ContextClassLoader詳解
ContextClassLoader
ContextClassLoader是一種與線程相關(guān)的類加載器,類似ThreadLocal,每個(gè)線程對(duì)應(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)前線程上下文類加載器為老的類加載器
上面的使用場(chǎng)景是什么?Java默認(rèn)的類加載機(jī)制是委托機(jī)制,但是有些時(shí)候需要破壞這種固定的機(jī)制
具體來說,比如Java中的SPI(Service Provider Interface)是面向接口編程的,服務(wù)規(guī)則提供者會(huì)在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ū)動(dòng)提供商提供的實(shí)現(xiàn)類,提供者只需在JDBC實(shí)現(xiàn)的jar的META-INF/services/java.sql.Driver文件里指定實(shí)現(xiàn)類的方式暴露驅(qū)動(dòng)提供者.例如:MYSQL實(shí)現(xiàn)的jar如下:

其中MYSQL的驅(qū)動(dòng)如下實(shí)現(xiàn)了java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver
引入MySQL驅(qū)動(dòng)的jar包,測(cè)試類如下:
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ū)動(dòng)則是放到我們的classpath下面.從上文知道默認(rèn)線程上下文類加載器就是AppClassLoader,所以例子里面沒有顯示在調(diào)用ServiceLoader前設(shè)置線程上下文類加載器為AppClassLoader,ServiceLoader內(nèi)部則獲取當(dāng)前線程上下文類加載器(這里為AppClassLoader)來加載服務(wù)實(shí)現(xiàn)者的類,這里加載了classpath下的MySQL的驅(qū)動(dòng)實(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,然后會(huì)嘗試使用ExtClassLoader去查找JDBC驅(qū)動(dòng)實(shí)現(xiàn),而ExtClassLoader掃描類的路徑為:JAVA_HOME/jre/lib/ext/,而這下面沒有驅(qū)動(dòng)實(shí)現(xiàn)的Jar,所以不會(huì)查找到驅(qū)動(dòng).
總結(jié)下,當(dāng)父類加載器需要加載子類加載器中的資源時(shí),可以通過設(shè)置和獲取線程上下文類加載器來實(shí)現(xiàn).
到此這篇關(guān)于Java類加載器之ContextClassLoader詳解的文章就介紹到這了,更多相關(guān)ContextClassLoader詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中配置ContextLoaderListener方式
這篇文章主要介紹了Spring中配置ContextLoaderListener方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
Session和JWT的區(qū)別是什么以及與Cookie相比詳解
Web應(yīng)用需客戶端存儲(chǔ)用戶信息,Cookie是早期方案,有特點(diǎn)及驗(yàn)證方式,下面這篇文章主要介紹了Session和JWT的區(qū)別是什么以及與Cookie相比的相關(guān)資料,文中通過代碼就介紹的非常詳細(xì),需要的朋友可以參考下2026-04-04
Java中實(shí)現(xiàn)二叉樹的遍歷與重構(gòu)
這篇文章主要介紹了Java中實(shí)現(xiàn)二叉樹的遍歷與重構(gòu),樹是一種非線性的數(shù)據(jù)結(jié)構(gòu),它是由n(n>=0)個(gè)有限結(jié)點(diǎn)組成一個(gè)具有層次關(guān)系的集合,把它叫做樹是因?yàn)樗雌饋硐褚豢玫箳斓臉?也就是說它是根朝上,而葉朝下的,需要的朋友可以參考下2023-10-10
SpringCloud Gateway實(shí)現(xiàn)限流功能詳解
SpringCloud Gateway 是 Spring Cloud 的一個(gè)全新項(xiàng)目,它旨在為微服務(wù)架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式。這篇文章主要介紹了SpringCloud Gateway實(shí)現(xiàn)限流,需要的朋友可以參考下2022-11-11
springboot整合sentinel接口熔斷的實(shí)現(xiàn)示例
為了防止慢接口導(dǎo)致的服務(wù)阻塞,可以通過添加熔斷處理來避免應(yīng)用的大量工作線程陷入阻塞,保證其他接口的正常運(yùn)行,本文介紹了如何使用Spring Boot與Sentinel進(jìn)行接口熔斷的配置與實(shí)現(xiàn),感興趣的可以了解一下2024-09-09
Java 集合實(shí)現(xiàn)分頁的方法(業(yè)務(wù)代碼實(shí)現(xiàn)分頁)
在Java開發(fā)中,有些場(chǎng)景比較復(fù)雜,受限制,不好在sql查詢層面實(shí)現(xiàn)分頁,需要在查詢的list結(jié)果后,將list分頁返回,如何實(shí)現(xiàn)呢,帶著這個(gè)問題一起通過本文學(xué)習(xí)吧2025-02-02
Java如何解析html中的內(nèi)容并存到數(shù)據(jù)庫詳解
最近用到了Java解析Html的一個(gè)庫Jsoup,所以下面這篇文章主要給大家介紹了關(guān)于Java如何解析html中的內(nèi)容并存到數(shù)據(jù)庫的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03

