Java中線程上下文類加載器超詳細(xì)講解使用
一、什么是線程上下文類加載器
線程上下文類加載器(Context Classloader)是從JDK1.2開始引入的,類Thread中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)分別用來獲取和設(shè)置上線文類加載器。
如果沒有通過setContextClassLoader(ClassLoader cl)進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。
Java應(yīng)用運(yùn)行時的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運(yùn)行的代碼可以通過該類加載器來加載類與資源。
1.1、重要性
它可以打破雙親委托機(jī)制,父ClassLoader可以使用當(dāng)前線程的Thread.currentThread().getContextClassLoader()所指定的classLoader來加載類,這就可以改變父ClassLoader不能使用子ClassLoader或是其他沒有直接父子關(guān)系的ClassLoader加載的類的情況,即改變了雙親委托模型
1.2、使用場景
對于SPI來說,有些接口是Java核心庫所提供的,而Java核心庫是由啟動類加載器加載的,而這些接口的實現(xiàn)卻是來自于不同jar包(廠商提供),Java的啟動類加載是不會加載其他來源的jar包,這樣傳統(tǒng)的雙親委托模型就無法滿足SPI的要求。而通過給當(dāng)前線程設(shè)置上下文類加載器,就可以由設(shè)置的上線文類加載器來實現(xiàn)與借口哦實現(xiàn)類的加載。
二、ServiceLoader簡單介紹
它是一個簡單的加載服務(wù)提供者的機(jī)制。通常服務(wù)提供者會實現(xiàn)服務(wù)當(dāng)中所定義的接口。服務(wù)提供者可以以一種擴(kuò)展的jar包的形式安裝到j(luò)ava平臺上擴(kuò)展目錄中,也可以添加到應(yīng)用的classpath中。
- 服務(wù)提供者需要提供一個無參數(shù)的構(gòu)造方法
- 服務(wù)提供者是通過在META-INF/services目錄下相應(yīng)的提供者配置文件,該配置文件的文件名由服務(wù)接口的包名組成。
- 提供者配置文件里面就是實現(xiàn)這個服務(wù)接口的類路徑,每個服務(wù)提供者占一行。
- ServiceLoader是按需加載和實例化提供者的,就是懶加載,ServiceLoader其中還包含一個服務(wù)提供者緩存,里面存放著已經(jīng)加載的服務(wù)提供者。
- ServiceLoader會返回一個iterator迭代器,會返回所有已經(jīng)加載了的服務(wù)提供者。
- ServiceLoader是線程不安全的
問題分析:
服務(wù)的接口通常是由啟動類加載器去加載的,那么它又是怎么去訪問到我們放在應(yīng)用classpath下的擴(kuò)展服務(wù)提供者的呢?
其內(nèi)部是通過掃描提供者配置文件,通過線程上下文類加載器來加載具體的實現(xiàn)類,線程上線文毋庸置疑默認(rèn)就是我們的系統(tǒng)類加載器,這樣就可以訪問到我們具體的服務(wù)提供者了。
三、案例
3.1、使用ServiceLoader加載mysql驅(qū)動
package com.brycen.classloader;
import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;
public class MyTest26 {
public static void main(String[] args) {
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()){
Driver dirver = iterator.next();
System.out.println(dirver.getClass()+", 類加載器:"+dirver.getClass().getClassLoader());
}
System.out.println("當(dāng)前線程上線文類加載器:"+Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader類加載器:"+loader.getClass().getClassLoader());
}
}運(yùn)行結(jié)果:
Driver接口的兩個實現(xiàn)類是由系統(tǒng)類加載器加載的,而我們的ServiceLoader類加載又是啟動類加載,此時正是因為使用線程類加載器中的系統(tǒng)類加載器。如果在加載之前,我們修改線程上線文類加載器為擴(kuò)展類加載器時,那我們的兩個實現(xiàn)類就加載不了了。
class com.mysql.jdbc.Driver, 類加載器:sun.misc.Launcher$AppClassLoader@18b4aac2
class com.mysql.fabric.jdbc.FabricMySQLDriver, 類加載器:sun.misc.Launcher$AppClassLoader@18b4aac2
當(dāng)前線程上線文類加載器:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader類加載器:null
3.2、Class.forName加載Mysql驅(qū)動
public class MyTest27 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//加載并初始化com.mysql.jdbc.Driver
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password");
}
}
3.2.1、com.mysql.jdbc.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
//靜態(tài)代碼塊,初始化的時候會執(zhí)行
static {
try {
//主動使用DriverManager,則該類也會初始化
//初始化完成后就調(diào)用DriverManager的registerDriver方法將自身添加到驅(qū)動集合中。
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
3.2.2、java.sql.DriverManager初始化
由于上面主動使用了DriverManager,那么該類也會初始化
public class DriverManager {
// 注冊JDBC驅(qū)動的集合
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
...
...
static {
//當(dāng)初始化的時候會執(zhí)行該方法
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
...
...
private static void loadInitialDrivers() {
String drivers;
//通過獲取系統(tǒng)參數(shù)來加載jdbc的驅(qū)動,如果沒有該參數(shù)則返回null
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//通過ServiceLoader來加載驅(qū)動,ServiceLoader已經(jīng)在上面講解過了
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
//這里會將加載到的驅(qū)動保存到上面的registeredDrivers集合中去
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
...
...3.2.3、調(diào)用DriverManager的registerDriver方法
當(dāng)我們的DriverManager初始化完成之后,com.mysql.jdbc.Driver中的靜態(tài)代碼塊就會執(zhí)行registerDriver方法,然后將自身注冊到registeredDrivers集合中去,這樣就完成了注冊驅(qū)動了
注:顯而易見,從DriverManager中的loadInitialDrivers我們可以得知,我們及時不使用Class.forName(“com.mysql.jdbc.Driver”),mysql的驅(qū)動也能被加載,這是因為后期jdk使用了ServiceLoader
...
...
//這個方法在com.mysql.jdbc.Driver初始化的時候被調(diào)用
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
//將驅(qū)動注冊到registeredDrivers集合中去
registerDriver(driver, null);
}
...
...
3.2.4、執(zhí)行DriverManager.getConnection方法
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
//封裝用戶名和密碼
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
//調(diào)用getConnection,并把基本信息和調(diào)用者的class(這里就是我們的MyTest27.class)
//Reflection.getCallerClass()是個本地方法,返回調(diào)用者的class
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
//這里獲取調(diào)用者的類加載器,如果為null則獲取線程上下文類加載
//從而實現(xiàn)能夠在DirverManager中訪問到放在我們classpath目錄下的驅(qū)動
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
//判斷每一個驅(qū)動是否有權(quán)限,這里的權(quán)限就是判斷該驅(qū)動的類加載器
//和上面獲取到的類加載器是否一致
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}到此這篇關(guān)于Java中線程上下文類加載器超詳細(xì)講解使用的文章就介紹到這了,更多相關(guān)Java線程上下文類加載器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Elasticsearch term 查詢之精確值搜索功能實現(xiàn)
term查詢是Elasticsearch中用于精確值搜索的一種基本方式,通過了解 term 查詢的工作原理和使用方法,你可以更好地利用 Elasticsearch 進(jìn)行結(jié)構(gòu)化數(shù)據(jù)的搜索和分析,本文將詳細(xì)介紹 term 查詢的工作原理、使用場景以及如何在 Elasticsearch 中應(yīng)用它,感興趣的朋友一起看看吧2024-06-06
使用IDEA創(chuàng)建Servlet程序的詳細(xì)步驟
在學(xué)習(xí)servlet過程中,參考的教程是用eclipse完成的,而我在練習(xí)的過程中是使用IDEA的,在創(chuàng)建servlet程序時遇到了挺多困難,在此記錄一下如何用IDEA完整創(chuàng)建一個servlet程序,感興趣的朋友一起看看吧2024-08-08
Spring?Boot如何監(jiān)控SQL運(yùn)行情況?
Druid是Java語言中最好的數(shù)據(jù)庫連接池,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot如何監(jiān)控SQL運(yùn)行情況的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
springboot jasypt2.x與jasypt3.x的使用方式
在軟件開發(fā)中,將配置文件中的敏感信息(如數(shù)據(jù)庫密碼)進(jìn)行加密是保障安全的有效手段,jasypt框架提供了這一功能,支持通過加密工具類或命令行工具生成密文,并通過修改配置文件和啟動參數(shù)的方式使用密文和密鑰,這樣即便配置文件被泄露2024-09-09
springboot 實現(xiàn)mqtt物聯(lián)網(wǎng)的示例代碼
這篇文章主要介紹了springboot 實現(xiàn)mqtt物聯(lián)網(wǎng),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03

