深入探索“線程上下文類加載器”

內(nèi)容概述

  • “線程上下文類加載器”介紹
  • SPI(Service Provider Interface)探索
  • 通過JDBC驅(qū)動(dòng)加載深刻理解線程上下文類加載器機(jī)制
    < br>

“線程上下文類加載器”介紹

線程上下問類加載器出現(xiàn)的原因

Q: 越基礎(chǔ)的類由越上層的加載器進(jìn)行加載晦雨,如果基礎(chǔ)類又要調(diào)用回用戶的代碼,那該怎么辦亭珍?
A: 解決方案:使用“線程上下文類加載器”

為了解決這個(gè)問題付燥,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)宣谈。這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoaser()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置键科,它將會(huì)從父線程中繼承一個(gè)闻丑,如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過的話,那這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器勋颖。

有了線程上下文類加載器嗦嗡,也就是父類加載器請(qǐng)求子類加載器去完成類加載的動(dòng)作(即,父類加載器加載的類饭玲,使用線程上下文加載器去加載其無法加載的類)侥祭,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,實(shí)際上已經(jīng)違背了雙親委派模型的一般性原則。
Java中所有涉及SPI的加載動(dòng)作基本上都采用這種方式卑硫,例如JNDI徒恋、JDBC、JCE欢伏、JAXB和JBI等入挣。

JDBC 使用偽代碼:

Class.forName("com.mysql.driver.Driver");
Connection conn = Driver.getConnection();
Statement st = conn.getStatement();

JDBC 是一個(gè)標(biāo)準(zhǔn)。不同的數(shù)據(jù)庫廠商(如硝拧,mysql径筏、oracle等)會(huì)根據(jù)這個(gè)標(biāo)準(zhǔn),有它們自己的實(shí)現(xiàn)障陶。
既然滋恬,JDBC 是一個(gè)標(biāo)準(zhǔn),那么 JDBC 的接口抱究,應(yīng)該就已經(jīng)存在與了 JDK 中了恢氯。(JDBC 相關(guān)的接口存在與 rt.jar 的java.sql 包下)

因此,JDBC 相關(guān)的這些接口鼓寺,在啟動(dòng)的時(shí)候勋拟,是由啟動(dòng)類加載器(boost classLoader)去加載的。
而通常妈候,我們會(huì)將數(shù)據(jù)庫廠商提供的 jar 包放置在 classPath 下敢靡,由此可知,數(shù)據(jù)庫廠商所提供的實(shí)現(xiàn)類不會(huì)由啟動(dòng)類加載器來去加載苦银,它們通常是由系統(tǒng)類加載器來去加載的啸胧。
這樣一來,接口是有啟動(dòng)類加載器加載的幔虏,而具體的實(shí)現(xiàn)是由應(yīng)用類加載器加載的纺念。根據(jù)類的雙親委托原則,父加載器所加載的類/接口是看不到子加載器所加載的類/接口的所计,而然柠辞,子加載器所加載的類/接口是能夠看到父加載器的類/接口的。這樣的話主胧,會(huì)導(dǎo)致這樣一個(gè)局面:JDBC 相關(guān)的代碼可能還需要去調(diào)用具體實(shí)現(xiàn)類中的代碼,但是它是無法看到具體的實(shí)現(xiàn)類的(因?yàn)槭怯善渥蛹虞d器加載的)习勤。
而這個(gè)問題踪栋,不僅是在 JDBC 中出現(xiàn),在 JNDI图毕、xml解析夷都,等場(chǎng)景下都會(huì)出現(xiàn)。
總結(jié)來說予颤,在 SPI 這種場(chǎng)合下都會(huì)出現(xiàn)的問題囤官。

Java 中所有涉及 SPI 的加載動(dòng)作基本上都采用這種方式冬阳,例如 JNDI、JDBC党饮、JCE肝陪、JAXB 和 JBI 等。

當(dāng)前類加載器(Current Classloader)

每個(gè)類都會(huì)使用自己的類加載器(即刑顺,加載自身的類加載器)來去加載其他的類(指的是所依賴的類)氯窍,如果 ClassX 引用了 ClassY,那么 ClassX 的類加載器就會(huì)去加載 ClassY(前提是 ClassY 尚未被加載)

線程上下文類加載器(Context Classloader)

線程上下文類加載器是從 JDK1.2 開始引入的蹲堂,類 Thread 中的 getContextClassLoader() 與 setContextClassLoader(ClassLoader cl) 分別用來獲取和設(shè)置上下文類加載器狼讨。
如果沒有通過 setContextClassLoader(ClassLoader cl) 進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器柒竞。
Java 應(yīng)用運(yùn)行時(shí)的初始線程的上下文類加載器是系統(tǒng)類加載器政供。在線程中運(yùn)行的代碼可以通過該類加載器來加載類與資源。

線程上下文類加載器的重要性:

SPI (Service Provider Interface ———— 服務(wù)提供者接口)
父ClassLoader 可以使用當(dāng)前線程 Thread.currentThread().getContextClassLoader() 所指定的 classloader 加載的類朽基。
這就改變了 父ClassLoader 不能使用 子ClassLoader 或是其他沒有直接父子關(guān)系的 ClassLoader 加載的類的情況布隔,即,改變了雙親委托模型踩晶。
線程上下文類加載器就是當(dāng)前線程的 Current ClassLoader执泰。
在雙親委托模型下,類加載器是由下至上的渡蜻,即下層的類加載器會(huì)委托上層進(jìn)行加載术吝。但是對(duì)于 SPI 來說,有些接口是 Java 核心庫所提供的茸苇,而 Java 核心庫是由啟動(dòng)類加載器來加載的排苍,而這些接口的實(shí)現(xiàn)卻來自于不同的 jar 包(廠商提供),Java 的啟動(dòng)類加載器是不會(huì)加載其他來源的 jar 包学密,這樣傳統(tǒng)的雙親委托模型就無法滿足 SPI 的要求淘衙。而通過給當(dāng)前線程設(shè)置上下文類加載器,就可以由設(shè)置的上下文類加載器來實(shí)現(xiàn)對(duì)于接口實(shí)現(xiàn)類的加載腻暮。

在框架開發(fā)彤守、底層組件開發(fā)、應(yīng)用服務(wù)器哭靖、web服務(wù)器的開發(fā)捌蚊,就會(huì)用到線程上下文類加載器叔扼。比如,tomcat 框架,就對(duì)加載器就做了比較大的改造媒怯。
tomcat 的類加載器是首先嘗試自己加載,自己加載不了才委托給它的雙親,這于傳統(tǒng)的雙親委托模型是相反的。

線程上下文類加載器的一般模式

線程上下文類加載器的一般使用模式(這個(gè)在框架中是大量應(yīng)用的):獲取 -> 使用 -> 還原

# 偽代碼:
    // 獲取
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    try {

        // 使用
        Thread.currentThread().setContextClassLoader(targetTccl);
        myMethod();

    } finally {

        // 還原
        Thread.currentThread().setContextClassLoader(classLoader);
    }

myMethod 里面則調(diào)用了 Thread.currentThread().getContextClassLoader()洲胖,獲取當(dāng)前線程的上下文類加載器做某些事情。

如果一個(gè)類由類加載器A加載坯沪,那么這個(gè)類的依賴也是由相同的類加載器加載的(如果該依賴類之前沒有被加載過的話)

ContextClassLoader 的作用就是為了破壞 Java 的類加載委托機(jī)制绿映。

當(dāng)高層提供了統(tǒng)一的接口讓低層去實(shí)現(xiàn),同時(shí)又要在高層加載(或?qū)嵗┑蛯拥念悤r(shí)屏箍,就必須要通過線程上下文類加載器來幫助高層的 ClassLoader 找到并加載該類绘梦。

如果我們沒有對(duì)線程上下文類加載器做任何設(shè)值的話,那么當(dāng)前線程的上下文類加載器就是"系統(tǒng)類加載器"赴魁。

Q:其實(shí)只要是能加載目標(biāo)類的任何加載器都可以實(shí)現(xiàn)打破委托模式的目的卸奉,那么為什么一定通過線程上下文類加載來實(shí)現(xiàn)了?
A:因?yàn)槿魏蔚?Java 代碼都是運(yùn)行某個(gè)線程上的颖御,因此將這個(gè)打破委托模式的類加載器放在線程中是最合適的榄棵。

SPI(Service Provider Interface)探索

SPI 全稱為 (Service Provider Interface) ,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制潘拱。SPI是一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制疹鳄, 比如有個(gè)接口,想運(yùn)行時(shí)動(dòng)態(tài)的給它添加實(shí)現(xiàn)芦岂,你只需要添加一個(gè)實(shí)現(xiàn)瘪弓。我們經(jīng)常遇到的就是java.sql.Driver接口,其他不同廠商可以針對(duì)同一接口做出不同的實(shí)現(xiàn)禽最,mysql和postgresql都有不同的實(shí)現(xiàn)提供給用戶腺怯,而Java的SPI機(jī)制可以為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)。

通過 MySql 來講解 SPI
  1. 添加 MySql 依賴
# 修改 gradle 文件
dependencies {
//    testCompile group: 'junit', name: 'junit', version: '4.12'

    compile (
            "mysql:mysql-connector-java:5.1.34"
    )
}
  1. ServiceLoader
    很多 SPI 的實(shí)現(xiàn)都是通過 ServiceLoader 來去完成的川无。
# MyTest26
public class MyTest26 {

    public static void main(String[] args) {

        // 很多 SPI 的實(shí)現(xiàn)都是通過 ServiceLoader 來去完成的呛占。
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);

        // JDK 的類加載器所能尋找到的所有驅(qū)動(dòng)
        Iterator<Driver> iterator = loader.iterator();

        while (iterator.hasNext()) {
            Driver driver = iterator.next();

            System.out.println("driver : " + driver.getClass() + " , loader : " + driver.getClass().getClassLoader());
        }

        System.out.println("當(dāng)前線程上線文類加載器 : " + Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader的類加載器 : " + ServiceLoader.class.getClassLoader());

    }
}

# 控制臺(tái)
driver : class com.mysql.jdbc.Driver , loader : sun.misc.Launcher$AppClassLoader@18b4aac2
driver : class com.mysql.fabric.jdbc.FabricMySQLDriver , loader : sun.misc.Launcher$AppClassLoader@18b4aac2
當(dāng)前線程上線文類加載器 : sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的類加載器 : null

??總共找到了 2 個(gè)驅(qū)動(dòng),都是從我們引入 mysql jar 包中找到的懦趋。
『driver : class com.mysql.jdbc.Driver , loader : sun.misc.Launcher$AppClassLoader@18b4aac2』:這個(gè)”com.mysql.jdbc.Driver"就是我們剛剛應(yīng)用進(jìn)來的 mysql 包里的類晾虑,也就是說,它的包在 classPath 中仅叫。

注意帜篇,從??的結(jié)果我們能發(fā)現(xiàn)。ServiceLoader 是由“啟動(dòng)類加載器”加載的诫咱,而它確認(rèn)能夠獲取到“系統(tǒng)類加載器”加載的’com.mysql.jdbc.Driver’和’com.mysql.fabric.jdbc.FabricMySQLDriver’坠狡。
而且,ServiceLoader 又是根據(jù)什么機(jī)制遂跟,如何找到的這兩個(gè)驅(qū)動(dòng)了??

那么幻锁,接下來我們來深入來接下 ServiceLoader 類

ServiceLoader

一個(gè)簡(jiǎn)單的加載服務(wù)器提供者的設(shè)施凯亮。(即,用于加載服務(wù)器提供者哄尔,也就是服務(wù)的具體實(shí)現(xiàn))

一個(gè)服務(wù)(service)是一個(gè)已知的接口和類(通常是抽象類)假消。 一個(gè)服務(wù)器提供者(service provider)就是服務(wù)(service)的一個(gè)具體實(shí)現(xiàn)。一個(gè)服務(wù)提供者類通常都會(huì)實(shí)現(xiàn)服務(wù)接口或子類化服務(wù)本身所定義的類岭接。 服務(wù)提供者能通過擴(kuò)展的方式安裝進(jìn) Java 平臺(tái)富拗,比如,以jar文件的形式放置到任何常規(guī)的擴(kuò)展目錄下鸣戴。而提供者還可以通過將其加到應(yīng)用的 classPath 或者特定于平臺(tái)的方式使其(即啃沪,jar包)變得可用。

處于加載的目標(biāo)窄锅,一個(gè)服務(wù)是由單個(gè)的類型來表示的创千,也就是說,一個(gè)單個(gè)的接口或者抽象類(一個(gè)具體的類也能夠被使用入偷,但這是不推薦的)追驴。一個(gè)給定服務(wù)的提供者它會(huì)包含一個(gè)或多個(gè)具體的類,這些類通過數(shù)據(jù)和提供者特有的代碼來擴(kuò)展服務(wù)類型疏之。 提供者類通常不是提供者本身殿雪,而是一個(gè)代理,它包含足夠的信息可以確定提供者是否可用滿足特定的一個(gè)需求并根據(jù)需要?jiǎng)?chuàng)建真實(shí)的提供者锋爪。提供者的細(xì)節(jié)與特定的服務(wù)類型高度相關(guān)丙曙,沒有單個(gè)的類或接口能夠?qū)⑺鼈儯矗械姆?wù)類型)統(tǒng)一起來几缭,因此沒有這樣的一個(gè)類型被定義在此河泳。這個(gè)設(shè)置(ServiceLoader)的唯一強(qiáng)制性需求是:提供者類必須要有一個(gè)無參數(shù)的構(gòu)造函數(shù),這樣它們可以在加載的時(shí)候就被實(shí)例化年栓。

服務(wù)提供者通過將一個(gè)‘provider-configuration’文件放置到資源目錄的『META-INF/services』下拆挥,而且文件的名字是服務(wù)類型的一個(gè)全限定的二進(jìn)制名。這個(gè)文件會(huì)包含具體提供者類的完全限定的二進(jìn)制名字的列表某抓,并每行放置一個(gè)纸兔。空格否副、tab鍵汉矿、以及空行都會(huì)被忽略掉。注釋字符是『#』备禀;位于第一個(gè)注釋的后的所有字符都被被忽略(即洲拇,注釋內(nèi)容會(huì)被忽略)奈揍。并且該文件必須是 UTF-8 編碼。
??這段說的是赋续,如果提供者是如果告訴JDK我所提供的具體類是什么的男翰。就是通過‘provider-configuration’(文件的真實(shí)名字是服務(wù)類型的一個(gè)全限定的二進(jìn)制名)配置文件來告知的,以及配置文件的格式是什么纽乱。

# ServiceLoader 中的靜態(tài)常量
private static final String PREFIX = "META-INF/services/";

如果一個(gè)具體的提供者類出現(xiàn)在了多個(gè)配置文件中蛾绎,或者一個(gè)具體提供者類的名字在一個(gè)文件中出現(xiàn)了多次,那么重復(fù)的就會(huì)被忽略掉鸦列。配置文件的名字是不需要位于相同的 jar 包中或者其他的分發(fā)單元里租冠。提供者必須能夠通過相同的類加載器(即,最初去定位這個(gè)配置文件的類加載器)來去加載出來的薯嗤;注意顽爹,但并不要求是真實(shí)加載文件的類加載器(??,即要求的是’初始加載器’应民,也就是請(qǐng)求加載類的加載器话原,并不要求是’定義類加載器’,也就是真實(shí)加載類的加載器)诲锹。

  • 通過查看 Mysql jar 包的 META-INF/services 目錄繁仁,以及 MyTest26 的程序結(jié)果。來驗(yàn)證上面的理論


# java.sql.Driver 文件 ——— 文件的名字是服務(wù)類型的一個(gè)全限定的二進(jìn)制名
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

提供者是延遲定位和實(shí)例化的归园,也就是按需來去定位和實(shí)例化的黄虱。一個(gè)服務(wù)加載器會(huì)維護(hù)著一個(gè)到目前為止已經(jīng)被加載的提供者的緩存列表。而庸诱,每一次對(duì)于 iterator 方法的調(diào)用都會(huì)返回一個(gè)迭代器捻浦,該迭代器首先會(huì)獲取緩存中所有的元素,并且獲取的順序?yàn)樵貙?shí)例化的順序桥爽。然后朱灿,延遲定位和實(shí)例化剩余的提供者,并且將其按照順序添加到緩沖中钠四。緩沖可以通過 reload 方法進(jìn)行清空盗扒。(代碼見??)

# ServiceLoader 中的成員屬性
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

這個(gè)類的實(shí)例對(duì)于多個(gè)并發(fā)線程來說并不是安全的。(即缀去,ServiceLoader 不是一個(gè)線程安全的類)

除非有其他的指定侣灶,傳入一個(gè) null 參數(shù)給該類中的任何方法都將導(dǎo)致一個(gè) NullPointerException 異常被拋出。

示例:

Suppose we have a service type com.example.CodecSet which is intended to represent sets of encoder/decoder pairs for some protocol. In this case it is an abstract class with two abstract methods:
   public abstract Encoder getEncoder(String encodingName);
   public abstract Decoder getDecoder(String encodingName);
Each method returns an appropriate object or null if the provider does not support the given encoding. Typical providers support more than one encoding.
If com.example.impl.StandardCodecs is an implementation of the CodecSet service then its jar file also contains a file named
   META-INF/services/com.example.CodecSet
This file contains the single line:
   com.example.impl.StandardCodecs    # Standard codecs
The CodecSet class creates and saves a single service instance at initialization:(CodecSet 類會(huì)保存一個(gè)單個(gè)服務(wù)的實(shí)例在初始化的時(shí)候)
   private static ServiceLoader<CodecSet> codecSetLoader
       = ServiceLoader.load(CodecSet.class);
To locate an encoder for a given encoding name it defines a static factory method which iterates through the known and available providers, returning only when it has located a suitable encoder or has run out of providers.
   public static Encoder getEncoder(String encodingName) {
       for (CodecSet cp : codecSetLoader) {
           Encoder enc = cp.getEncoder(encodingName);
           if (enc != null)
               return enc;
       }
       return null;
   }
A getDecoder method is defined similarly.

使用注意缕碎,如果一個(gè)用于加載提供者的 類加載器 的 classpath 包含遠(yuǎn)程的網(wǎng)絡(luò)url褥影,那么在搜索‘provider-configuration’文件的時(shí)候這些url將會(huì)被解引用。

ServiceLoader中的’泛型’就是咏雌,服務(wù)加載器想要去加載的服務(wù)類型凡怎。

MyTest26 代碼講解

ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
主要完成了:
① ServiceLoader 的實(shí)例化校焦。即,構(gòu)建了一個(gè) ServiceLoader 對(duì)象
② 構(gòu)建了一個(gè) LazyIterator

  1. public static <S> ServiceLoader<S> load(Class<S> service)
  • private ServiceLoader(Class<S> svc, ClassLoader cl) ———— 構(gòu)造方法

  • public void reload()

  1. Iterator<Driver> iterator = loader.iterator();


MyTest26 示例修改

因?yàn)?擴(kuò)展類加載器 是加載不到我們 MySql 的驅(qū)動(dòng)的栅贴,因此 iterator.hasNext() 返回 false斟湃。

通過JDBC驅(qū)動(dòng)加載深刻理解線程上下文類加載器機(jī)制

示例
# MyTest27
public class MyTest27 {

    public static void main(String[] args) throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytestdb", "username", "password");
    }
}
第一步

Class.forName("com.mysql.jdbc.Driver");
會(huì)去加載并初始化“com.mysql.jdbc.Driver”

# com.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”的過程會(huì)去:
a)初始化“java.sql.DriverManager”(啟動(dòng)類加載器加載的)檐薯。它會(huì):
① 加載“jdbc.properties”屬性所指定的服務(wù)。
② 同時(shí)也會(huì)注暗,使用 ServiceLoader 機(jī)制(即坛缕,使用線程上下文類加載器(本例中即為系統(tǒng)類加載器)來加載指定目錄下(META-INF/services)指定服務(wù)類型的服務(wù)提供者類)來加載“java.sql.Driver” 類型的服務(wù)提供類(本例為 com.mysql.jdbc.Driver、com.mysql.fabric.jdbc.FabricMySQLDriver)捆昏。這樣使得赚楚,在java.sql.DriverManager 的Class對(duì)象中就可以使用這些服務(wù)提供者類了。(所以骗卜,第二步的’getConnection’就是一個(gè)靜態(tài)方法宠页。)
注意,這里就是啟動(dòng)類加載器加載的類可以使用線程上下文類加載器加載的類了寇仓。其實(shí)就是通過線程上下文類加載器举户,將啟動(dòng)類加載器不可見的類加載了進(jìn)來,這樣啟動(dòng)類加載器加載的這個(gè)類就可以去使用它們了遍烦。
b)將 com.mysql.jdbc.Driver 的實(shí)例注冊(cè)到 java.sql.DriverManager 中

  • a)初始化“java.sql.DriverManager”
# java.sql.DriverManager
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
private static volatile int loginTimeout = 0;
private static volatile java.io.PrintWriter logWriter = null;
private static volatile java.io.PrintStream logStream = null;
// Used in println() to synchronize logWriter
private final static  Object logSync = new Object();
/**
 * 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");
}
private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    // 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;
        }
    });

    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);
        }
    }
}

初始化“java.sql.DriverManager”的過程:
加載 java.sql.Driver 類型的服務(wù)提供類俭嘁。注意,需要調(diào)用 Iterator的hasNext()和next()方法才能實(shí)現(xiàn)對(duì)“java.sql.Driver的服務(wù)提供類”的初始化服猪。

第二步

Connection conn = DriverManager.getConnection("jdbc:[mysql://localhost:3306/mytestdb](mysql://localhost:3306/mytestdb)", "username", "password”);
該方法底層要求供填,調(diào)用該方法對(duì)象的類加載器,要和執(zhí)行“Class.forName("com.mysql.jdbc.Driver");”方法線程的上下文類加載器是同一個(gè)類加載器罢猪。
也就是說近她,保證了加載 服務(wù)提供者類的類加載器 與 執(zhí)行“DriverManager.getConnection(...);”方法的對(duì)象的類加載器是同一個(gè)類加載器。
這是因?yàn)樯排粒癉riverManager.getConnection(…)”方法中會(huì)去使用 服務(wù)提供者類

同時(shí)值得關(guān)注的是粘捎,借用該方式,就可以在 java.sql.DriverManager 對(duì)象(由备闲,啟動(dòng)類加載器加載的)類調(diào)用到服務(wù)提供者類(由線程上下文類加載器加載的晌端,這里即為系統(tǒng)類加載器)的方法了。


最后恬砂,其實(shí)在該例子中咧纠。都可以將 第一步(即,Class.forName("com.mysql.jdbc.Driver”); ———— 加載并初始化com.mysql.jdbc.Driver) 去除泻骤。也能達(dá)到一樣的效果

這是因?yàn)?“DriverManager.getConnection(...)”方法會(huì)導(dǎo)致漆羔,DriverManager的加載和初始化梧奢,而在DriverManager初始化過程中,會(huì)有去加載(包括’初始化’) com.mysql.jdbc.Driver 類型的服務(wù)提供者類(com.mysql.jdbc.Driver演痒、com.mysql.fabric.jdbc.FabricMySQLDriver)亲轨。

相關(guān)文章

深入淺出“類加載器”
從 sun.misc.Launcher 類源碼深入探索 ClassLoader
ClassLoader 源碼詳解

參考

圣思園《深入理解JVM》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸟顺,隨后出現(xiàn)的幾起案子惦蚊,更是在濱河造成了極大的恐慌,老刑警劉巖讯嫂,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹦锋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡欧芽,警方通過查閱死者的電腦和手機(jī)莉掂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來千扔,“玉大人憎妙,你說我怎么就攤上這事∏” “怎么了厘唾?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)洞渤。 經(jīng)常有香客問我阅嘶,道長(zhǎng),這世上最難降的妖魔是什么载迄? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任讯柔,我火速辦了婚禮,結(jié)果婚禮上护昧,老公的妹妹穿的比我還像新娘魂迄。我一直安慰自己,他們只是感情好惋耙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布捣炬。 她就那樣靜靜地躺著,像睡著了一般绽榛。 火紅的嫁衣襯著肌膚如雪湿酸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天灭美,我揣著相機(jī)與錄音推溃,去河邊找鬼。 笑死届腐,一個(gè)胖子當(dāng)著我的面吹牛铁坎,可吹牛的內(nèi)容都是我干的蜂奸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼硬萍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼扩所!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起朴乖,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤祖屏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寒砖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赐劣,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年哩都,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婉徘。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漠嵌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盖呼,到底是詐尸還是另有隱情儒鹿,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布几晤,位于F島的核電站约炎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蟹瘾。R本人自食惡果不足惜圾浅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望憾朴。 院中可真熱鬧狸捕,春花似錦、人聲如沸众雷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砾省。三九已至鸡岗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間编兄,已是汗流浹背轩性。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翻诉,地道東北人炮姨。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓捌刮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親舒岸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绅作,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容