Java類加載器之ContextClassLoader詳解
ContextClassLoader
ContextClassLoader是一種與線程相關的類加載器,類似ThreadLocal,每個線程對應一個上下文類加載器.在實際使用時一般都用下面的經(jīng)典結構:
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);
}- 首先獲取當前線程的線程上下文類加載器并保存到方法棧,然后將外部傳遞的類加載器設置為當前線程上下文類加載器
- doSomething則可以利用新設置的類加載器做一些事情
- 最后在設置當前線程上下文類加載器為老的類加載器
上面的使用場景是什么?Java默認的類加載機制是委托機制,但是有些時候需要破壞這種固定的機制
具體來說,比如Java中的SPI(Service Provider Interface)是面向接口編程的,服務規(guī)則提供者會在JRE的核心API里面提供服務訪問接口,而具體的實現(xiàn)則由其他開發(fā)商提供.我們知道Java核心API,比如rt.jar包,是使用Bootstrap ClassLoader加載的,而用戶提供的jar包再有AppClassLoader加載.并且我們知道一個類由類加載器A加載,那么這個類依賴類也應該由相同的類加載器加載.那么Bootstrap ClassLoader加載了服務提供者在rt.jar里面提供的搜索開發(fā)上提供的實現(xiàn)類的API類(ServiceLoader),那么這些API類里面依賴的類應該也是有Bootstrap ClassLoader來加載.而上面說了用戶提供的Jar包有AppClassLoader加載,所以需要一種違反雙親委派模型的方法,線程上下文類加載器ContextClassLoader就是為了解決這個問題.
下面使用JDBC來具體說明,JDBC是基于SPI機制來發(fā)現(xiàn)驅動提供商提供的實現(xiàn)類,提供者只需在JDBC實現(xiàn)的jar的META-INF/services/java.sql.Driver文件里指定實現(xiàn)類的方式暴露驅動提供者.例如:MYSQL實現(xiàn)的jar如下:

其中MYSQL的驅動如下實現(xiàn)了java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver
引入MySQL驅動的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í)行結果如下:
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í)行結果中可以知道ServiceLoader的加載器為Bootstrap,因為這里輸出了null,并且從該類在rt.jar里面,也可以證明.
當前線程上下文類加載器為AppClassLoader.而com.mysql.jdbc.Driver則使用AppClassLoader加載.我們知道如果一個類中引用了另外一個類,那么被引用的類也應該由引用方類加載器來加載,而現(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) {
// 獲取當前線程上下文加載器,這里是APPClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}上述代碼獲得了線程上下文加載器(其實就是AppClassLoader),并將該類加載器傳遞到下面的ServiceLoader類的構造方法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變量什么時候使用的?看一下下面的代碼:
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類加載委托機制,JDBC規(guī)范定義了一個JDBC接口,然后使用SPI機制提供的一個叫做ServiceLoader的Java核心API(rt.jar里面提供)用來掃描服務實現(xiàn)類,服務實現(xiàn)者提供的jar,比如MySQL驅動則是放到我們的classpath下面.從上文知道默認線程上下文類加載器就是AppClassLoader,所以例子里面沒有顯示在調(diào)用ServiceLoader前設置線程上下文類加載器為AppClassLoader,ServiceLoader內(nèi)部則獲取當前線程上下文類加載器(這里為AppClassLoader)來加載服務實現(xiàn)者的類,這里加載了classpath下的MySQL的驅動實現(xiàn).
可以嘗試在調(diào)用ServiceLoader的load方法前設置線程上下文類加載器為ExtClassLoader,代碼如下:
Thread.currentThread().setContextClassLoader(ContextClassLoaderTest.class.getClassLoader().getParent());
然后運行本例子,設置后ServiceLoader內(nèi)部則獲取當前線程上下文類加載器為ExtClassLoader,然后會嘗試使用ExtClassLoader去查找JDBC驅動實現(xiàn),而ExtClassLoader掃描類的路徑為:JAVA_HOME/jre/lib/ext/,而這下面沒有驅動實現(xiàn)的Jar,所以不會查找到驅動.
總結下,當父類加載器需要加載子類加載器中的資源時,可以通過設置和獲取線程上下文類加載器來實現(xiàn).
到此這篇關于Java類加載器之ContextClassLoader詳解的文章就介紹到這了,更多相關ContextClassLoader詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java利用StringBuffer替換特殊字符的方法實現(xiàn)
這篇文章主要介紹了Java利用StringBuffer替換特殊字符的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04
詳解Java如何實現(xiàn)在PDF中插入,替換或刪除圖像
圖文并茂的內(nèi)容往往讓人看起來更加舒服,如果只是文字內(nèi)容的累加,往往會使讀者產(chǎn)生視覺疲勞。搭配精美的文章配圖則會使文章內(nèi)容更加豐富。那我們要如何在PDF中插入、替換或刪除圖像呢?別擔心,今天為大家介紹一種高效便捷的方法2023-01-01
Java中的Valid和Validated的比較內(nèi)容
在本篇文章里小編給大家整理的是關于Java中的Valid和Validated的比較內(nèi)容,對此有興趣的朋友們可以學習參考下。2021-02-02
SpringBoot集成MyBatis的分頁插件PageHelper實例代碼
這篇文章主要介紹了SpringBoot集成MyBatis的分頁插件PageHelper的相關操作,需要的朋友可以參考下2017-08-08

