Java中線程上下文類加載器超詳細(xì)講解使用
一、什么是線程上下文類加載器
線程上下文類加載器(Context Classloader)是從JDK1.2開(kāi)始引入的,類Thread中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)分別用來(lái)獲取和設(shè)置上線文類加載器。
如果沒(méi)有通過(guò)setContextClassLoader(ClassLoader cl)進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。
Java應(yīng)用運(yùn)行時(shí)的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運(yùn)行的代碼可以通過(guò)該類加載器來(lái)加載類與資源。
1.1、重要性
它可以打破雙親委托機(jī)制,父ClassLoader可以使用當(dāng)前線程的Thread.currentThread().getContextClassLoader()所指定的classLoader來(lái)加載類,這就可以改變父ClassLoader不能使用子ClassLoader或是其他沒(méi)有直接父子關(guān)系的ClassLoader加載的類的情況,即改變了雙親委托模型
1.2、使用場(chǎng)景
對(duì)于SPI來(lái)說(shuō),有些接口是Java核心庫(kù)所提供的,而Java核心庫(kù)是由啟動(dòng)類加載器加載的,而這些接口的實(shí)現(xiàn)卻是來(lái)自于不同jar包(廠商提供),Java的啟動(dòng)類加載是不會(huì)加載其他來(lái)源的jar包,這樣傳統(tǒng)的雙親委托模型就無(wú)法滿足SPI的要求。而通過(guò)給當(dāng)前線程設(shè)置上下文類加載器,就可以由設(shè)置的上線文類加載器來(lái)實(shí)現(xiàn)與借口哦實(shí)現(xiàn)類的加載。
二、ServiceLoader簡(jiǎn)單介紹
它是一個(gè)簡(jiǎn)單的加載服務(wù)提供者的機(jī)制。通常服務(wù)提供者會(huì)實(shí)現(xiàn)服務(wù)當(dāng)中所定義的接口。服務(wù)提供者可以以一種擴(kuò)展的jar包的形式安裝到j(luò)ava平臺(tái)上擴(kuò)展目錄中,也可以添加到應(yīng)用的classpath中。
- 服務(wù)提供者需要提供一個(gè)無(wú)參數(shù)的構(gòu)造方法
- 服務(wù)提供者是通過(guò)在META-INF/services目錄下相應(yīng)的提供者配置文件,該配置文件的文件名由服務(wù)接口的包名組成。
- 提供者配置文件里面就是實(shí)現(xiàn)這個(gè)服務(wù)接口的類路徑,每個(gè)服務(wù)提供者占一行。
- ServiceLoader是按需加載和實(shí)例化提供者的,就是懶加載,ServiceLoader其中還包含一個(gè)服務(wù)提供者緩存,里面存放著已經(jīng)加載的服務(wù)提供者。
- ServiceLoader會(huì)返回一個(gè)iterator迭代器,會(huì)返回所有已經(jīng)加載了的服務(wù)提供者。
- ServiceLoader是線程不安全的
問(wèn)題分析:
服務(wù)的接口通常是由啟動(dòng)類加載器去加載的,那么它又是怎么去訪問(wèn)到我們放在應(yīng)用classpath下的擴(kuò)展服務(wù)提供者的呢?
其內(nèi)部是通過(guò)掃描提供者配置文件,通過(guò)線程上下文類加載器來(lái)加載具體的實(shí)現(xiàn)類,線程上線文毋庸置疑默認(rèn)就是我們的系統(tǒng)類加載器,這樣就可以訪問(wèn)到我們具體的服務(wù)提供者了。
三、案例
3.1、使用ServiceLoader加載mysql驅(qū)動(dòng)
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接口的兩個(gè)實(shí)現(xiàn)類是由系統(tǒng)類加載器加載的,而我們的ServiceLoader類加載又是啟動(dòng)類加載,此時(shí)正是因?yàn)槭褂镁€程類加載器中的系統(tǒng)類加載器。如果在加載之前,我們修改線程上線文類加載器為擴(kuò)展類加載器時(shí),那我們的兩個(gè)實(shí)現(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ū)動(dòng)
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)代碼塊,初始化的時(shí)候會(huì)執(zhí)行 static { try { //主動(dòng)使用DriverManager,則該類也會(huì)初始化 //初始化完成后就調(diào)用DriverManager的registerDriver方法將自身添加到驅(qū)動(dòng)集合中。 DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }
3.2.2、java.sql.DriverManager初始化
由于上面主動(dòng)使用了DriverManager,那么該類也會(huì)初始化
public class DriverManager { // 注冊(cè)JDBC驅(qū)動(dòng)的集合 private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>(); ... ... static { //當(dāng)初始化的時(shí)候會(huì)執(zhí)行該方法 loadInitialDrivers(); println("JDBC DriverManager initialized"); } ... ... private static void loadInitialDrivers() { String drivers; //通過(guò)獲取系統(tǒng)參數(shù)來(lái)加載jdbc的驅(qū)動(dòng),如果沒(méi)有該參數(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() { //通過(guò)ServiceLoader來(lái)加載驅(qū)動(dòng),ServiceLoader已經(jīng)在上面講解過(guò)了 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ //這里會(huì)將加載到的驅(qū)動(dòng)保存到上面的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)代碼塊就會(huì)執(zhí)行registerDriver方法,然后將自身注冊(cè)到registeredDrivers集合中去,這樣就完成了注冊(cè)驅(qū)動(dòng)了
注:顯而易見(jiàn),從DriverManager中的loadInitialDrivers我們可以得知,我們及時(shí)不使用Class.forName(“com.mysql.jdbc.Driver”),mysql的驅(qū)動(dòng)也能被加載,這是因?yàn)楹笃趈dk使用了ServiceLoader
... ... //這個(gè)方法在com.mysql.jdbc.Driver初始化的時(shí)候被調(diào)用 public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { //將驅(qū)動(dòng)注冊(cè)到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()是個(gè)本地方法,返回調(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則獲取線程上下文類加載 //從而實(shí)現(xiàn)能夠在DirverManager中訪問(wèn)到放在我們classpath目錄下的驅(qū)動(dòng) 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) { //判斷每一個(gè)驅(qū)動(dòng)是否有權(quán)限,這里的權(quán)限就是判斷該驅(qū)動(dòng)的類加載器 //和上面獲取到的類加載器是否一致 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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea在用Mybatis時(shí)xml文件sql不提示解決辦法(提示后背景顏色去除)
mybatis的xml文件配置的時(shí)候,有時(shí)候會(huì)沒(méi)有提示,這讓我們很頭疼,下面這篇文章主要給大家介紹了關(guān)于idea在用Mybatis時(shí)xml文件sql不提示的解決辦法,提示后背景顏色去除的相關(guān)資料,需要的朋友可以參考下2023-03-03java利用遞歸算法實(shí)現(xiàn)對(duì)文件夾的刪除功能
這篇文章主要介紹了java利用遞歸算法實(shí)現(xiàn)對(duì)文件夾的刪除功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09利用MyBatis-Plus靈活處理JSON字段的技巧與最佳實(shí)踐
這篇文章主要給大家介紹了關(guān)于利用MyBatis-Plus靈活處理JSON字段的技巧與最佳實(shí)踐,Mybatis-Plus可以很方便地處理JSON字段,在實(shí)體類中可以使用@JSONField注解來(lái)標(biāo)記JSON字段,需要的朋友可以參考下2024-07-07java實(shí)現(xiàn)將字符串中首字母轉(zhuǎn)換成大寫,其它全部轉(zhuǎn)換成小寫的方法示例
這篇文章主要介紹了java實(shí)現(xiàn)將字符串中首字母轉(zhuǎn)換成大寫,其它全部轉(zhuǎn)換成小寫的方法,涉及java字符串遍歷、轉(zhuǎn)換、拼接等相關(guān)操作技巧,需要的朋友可以參考下2019-06-06