首頁 > 軟體

Java中執行緒上下文類載入器超詳細講解使用

2022-12-23 14:01:02

一、什麼是執行緒上下文類載入器

執行緒上下文類載入器(Context Classloader)是從JDK1.2開始引入的,類Thread中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)分別用來獲取和設定上線文類載入器。

如果沒有通過setContextClassLoader(ClassLoader cl)進行設定的話,執行緒將繼承其父執行緒的上下文類載入器。

Java應用執行時的初始執行緒的上下文類載入器是系統類載入器。線上程中執行的程式碼可以通過該類載入器來載入類與資源。

1.1、重要性

它可以打破雙親委託機制,父ClassLoader可以使用當前執行緒的Thread.currentThread().getContextClassLoader()所指定的classLoader來載入類,這就可以改變父ClassLoader不能使用子ClassLoader或是其他沒有直接父子關係的ClassLoader載入的類的情況,即改變了雙親委託模型

1.2、使用場景

對於SPI來說,有些介面是Java核心庫所提供的,而Java核心庫是由啟動類載入器載入的,而這些介面的實現卻是來自於不同jar包(廠商提供),Java的啟動類載入是不會載入其他來源的jar包,這樣傳統的雙親委託模型就無法滿足SPI的要求。而通過給當前執行緒設定上下文類載入器,就可以由設定的上線文類載入器來實現與藉口哦實現類的載入。

二、ServiceLoader簡單介紹

它是一個簡單的載入服務提供者的機制。通常服務提供者會實現服務當中所定義的介面。服務提供者可以以一種擴充套件的jar包的形式安裝到java平臺上擴充套件目錄中,也可以新增到應用的classpath中。

  • 服務提供者需要提供一個無引數的構造方法
  • 服務提供者是通過在META-INF/services目錄下相應的提供者組態檔,該組態檔的檔名由服務介面的包名組成。
  • 提供者組態檔裡面就是實現這個服務介面的類路徑,每個服務提供者佔一行。
  • ServiceLoader是按需載入和範例化提供者的,就是懶載入,ServiceLoader其中還包含一個服務提供者快取,裡面存放著已經載入的服務提供者。
  • ServiceLoader會返回一個iterator迭代器,會返回所有已經載入了的服務提供者。
  • ServiceLoader是執行緒不安全的

問題分析:

服務的介面通常是由啟動類載入器去載入的,那麼它又是怎麼去存取到我們放在應用classpath下的擴充套件服務提供者的呢?

其內部是通過掃描提供者組態檔,通過執行緒上下文類載入器來載入具體的實現類,執行緒上線文毋庸置疑預設就是我們的系統類載入器,這樣就可以存取到我們具體的服務提供者了。

三、案例

3.1、使用ServiceLoader載入mysql驅動

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("當前執行緒上線文類載入器:"+Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader類載入器:"+loader.getClass().getClassLoader());
    }
}

執行結果:

Driver介面的兩個實現類是由系統類載入器載入的,而我們的ServiceLoader類載入又是啟動類載入,此時正是因為使用執行緒類載入器中的系統類載入器。如果在載入之前,我們修改執行緒上線文類載入器為擴充套件類載入器時,那我們的兩個實現類就載入不了了。

class com.mysql.jdbc.Driver, 類載入器:sun.misc.Launcher$AppClassLoader@18b4aac2
class com.mysql.fabric.jdbc.FabricMySQLDriver, 類載入器:sun.misc.Launcher$AppClassLoader@18b4aac2
當前執行緒上線文類載入器:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader類載入器:null

3.2、Class.forName載入Mysql驅動

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 {
    }
	//靜態程式碼塊,初始化的時候會執行
    static {
        try {
        	//主動使用DriverManager,則該類也會初始化
        	//初始化完成後就呼叫DriverManager的registerDriver方法將自身新增到驅動集合中。
            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驅動的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    ...
	...
    static {
    	//當初始化的時候會執行該方法
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    ...
    ...
    private static void loadInitialDrivers() {
        String drivers;
        //通過獲取系統引數來載入jdbc的驅動,如果沒有該引數則返回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來載入驅動,ServiceLoader已經在上面講解過了
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                	//這裡會將載入到的驅動儲存到上面的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、呼叫DriverManager的registerDriver方法

當我們的DriverManager初始化完成之後,com.mysql.jdbc.Driver中的靜態程式碼塊就會執行registerDriver方法,然後將自身註冊到registeredDrivers集合中去,這樣就完成了註冊驅動了

注:顯而易見,從DriverManager中的loadInitialDrivers我們可以得知,我們及時不使用Class.forName(“com.mysql.jdbc.Driver”),mysql的驅動也能被載入,這是因為後期jdk使用了ServiceLoader

...
...
//這個方法在com.mysql.jdbc.Driver初始化的時候被呼叫
public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {
	//將驅動註冊到registeredDrivers集合中去
    registerDriver(driver, null);
}
...
...

3.2.4、執行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);
    }
	//呼叫getConnection,並把基本資訊和呼叫者的class(這裡就是我們的MyTest27.class)
	//Reflection.getCallerClass()是個本地方法,返回撥用者的class
    return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    //這裡獲取呼叫者的類載入器,如果為null則獲取執行緒上下文類載入
    //從而實現能夠在DirverManager中存取到放在我們classpath目錄下的驅動
    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) {
        //判斷每一個驅動是否有許可權,這裡的許可權就是判斷該驅動的類載入器
        //和上面獲取到的類載入器是否一致
        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");
}

到此這篇關於Java中執行緒上下文類載入器超詳細講解使用的文章就介紹到這了,更多相關Java執行緒上下文類載入器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com