天天看點

線程上下文加載器Context ClassLoader的作用

先以一段mysql 資料庫連接配接的代碼作為引子

public class MysqlDriverTest {
    public static void main(String[] args) throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
    }
}
           

在學習mysql 的資料庫操作時,我們經常會使用上面的代碼來擷取資料庫連接配接。

大家是否想過 Class.forName("com.mysql.jdbc.Driver") 這段代碼的意義在哪?我們跟進去看一下

/**
     */
    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

    /** Called after security check for system loader access checks have been made.     */
    private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
        throws ClassNotFoundException;
           

由于調用的forName0 方法中,傳入的initalize 是true,是以Class.forName("com.mysql.jdbc.Driver") 實際上是加載,連接配接并初始化com.mysql.jdbc.Driver 。既然初始化om.mysql.jdbc.Driver 了這個類,必然會調用其靜态代碼塊。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}
           

靜态代碼塊中,執行個體化了一個com.mysql.jdbc.Driver 對象放到java.sql.DriverManager 類的類變量registeredDrivers 中。這段代碼會觸發java.sql.DriverManager 類的加載(如果還沒有加載)和初始化。

假如我們不先執行 Class.forName("com.mysql.jdbc.Driver") 會發生什麼?

我們就無法觸發com.mysql.jdbc.Driver 的初始化,不會調用其靜态代碼塊,是以也不會注冊該類的執行個體到DriverManager 中,是以就無法擷取到mysql 連接配接。

上面這段話,至少在jdk1.6 之前是正确的,但是在jdk1.6 之後,就錯了,代碼可以簡化成下面:

public class MysqlDriverTest {
    public static void main(String[] args) throws Exception {
//        Class.forName("com.mysql.jdbc.Driver");
        DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
    }
}
           

我們來分析一下,先來對比下jdk.15 和 jdk1.6 中,jdk 提供的DriverManager 中代碼的部分差別:

public class DriverManager {

    ....
    ....
    /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    ....
    ....
}
           

jdk1.5 是沒有上面這段代碼,從1.6 開始,就有了。這段代碼的主要作用就是利用SPI 去加載各個廠商提供的Driver 實作類。是以我們不用顯式的調用Class.forName("com.mysql.jdbc.Driver") 來加載com.mysql.jdbc.Driver,利用SPI 功能會自動去幫我們加載。

到這裡,好像都跟咱們的标題:線程上下文類加載器 沒有關系,接下來咱們繼續往下講。

是以當我們main 方法執行時,執行 DriverManager.getConnection(....),DriverManager 最終會交給啟動類加載器來加載,而Driver  接口實作類是由廠商實作的,其實作類放在classPath 下,由系統類加載器加載,根據雙親委托模型,父加載器的命名空間中的類是無法看到子加載器的命名空間中的類,是以DriverManager 是無法加載各個廠商提供的Driver 實作類。

但是,有了線程上下文類加載器Context ClassLoader 就不一樣類,假設我們都知道(不知道可以學習下類加載器相關),我們程式運作時,線程上下文類加載器預設是系統類加載器,我們就可以在DriverManager 類裡面擷取目前線程的線程上下文類加載器,來加載廠商提供的實作類。這樣就避免雙親委托模型的父加載器無法加載子加載器負責的類的尴尬局面,可以說是對雙親委托模型的一個補充。

我們可以看看jdk 1.6隻有 DriverManager 是怎麼利用上下文線程類加載器加載廠商提供的Driver 實作類。跟進去上面代碼提到的靜态代碼塊,省略不重要的代碼。

private static void loadInitialDrivers() {
        ...
        ...
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
        ...
        ...
    }
           

代碼中利用了SPI 加載廠商提供的實作類 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class),跟進load 方法

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
           

從這段代碼,可以看出,SPI 擷取了線程上下文類加載器,預設是系統類加載器,來加載廠商提供的實作類。具體我們看ServiceLoader 内部類 LazyIterator 的 nextService() 方法

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                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
        }
           

代碼中的loader 就是之前擷取的線程上下文類加載器。

是以我們可以發現,SPI 的功能是利用線程上下文類加載器,使得 父類加載器 能加載 子類加載器 甚至 其他沒有父子關系的類加載器 負責的類。