Java線程上下文加載器與SPI

SPI機制

SPI的全名為Service Provider Interface.大多數(shù)開發(fā)人員可能不熟悉悬槽,因為這個是針對廠商或者插件的。在java.util.ServiceLoader的文檔里有比較詳細的介紹。簡單的總結(jié)下java spi機制的思想光稼。我們系統(tǒng)里抽象的各個模塊枷踏,往往有很多不同的實現(xiàn)方案,比如日志模塊的方案惑折,xml解析模塊、jdbc模塊的方案等枯跑。面向的對象的設(shè)計里惨驶,我們一般推薦模塊之間基于接口編程,模塊之間不對實現(xiàn)類進行硬編碼敛助。一旦代碼里涉及具體的實現(xiàn)類粗卜,就違反了可拔插的原則,如果需要替換一種實現(xiàn)纳击,就需要修改代碼续扔。為了實現(xiàn)在模塊裝配的時候能不在程序里動態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機制评疗。 java spi就是提供這樣的一個機制:為某個接口尋找服務(wù)實現(xiàn)的機制测砂。有點類似IOC的思想,就是將裝配的控制權(quán)移到程序之外百匆,在模塊化設(shè)計中這個機制尤其重要砌些。

SPI具體約定

java spi的具體約定為:當服務(wù)的提供者,提供了服務(wù)接口的一種實現(xiàn)之后,在jar包的META-INF/services/目錄里同時創(chuàng)建一個以服務(wù)接口命名的文件存璃。該文件里就是實現(xiàn)該服務(wù)接口的具體實現(xiàn)類仑荐。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/里的配置文件找到具體的實現(xiàn)類名纵东,并裝載實例化粘招,完成模塊的注入。 基于這樣一個約定就能很好的找到服務(wù)接口的實現(xiàn)類偎球,而不需要再代碼里制定洒扎。jdk提供服務(wù)實現(xiàn)查找的一個工具類:java.util.ServiceLoader

應(yīng)用場景

  1. common-logging:apache最早提供的日志的門面接口。只有接口衰絮,沒有實現(xiàn)袍冷。具體方案由各提供商實現(xiàn), 發(fā)現(xiàn)日志提供商是通過掃描 META-INF/services/org.apache.commons.logging.LogFactory配置文件猫牡,通過讀取該文件的內(nèi)容找到日志提工商實現(xiàn)類胡诗。只要我們的日志實現(xiàn)里包含了這個文件,并在文件里制定LogFactory工廠接口的實現(xiàn)類即可淌友。
  2. JDBC:jdbc4.0以前煌恢, 開發(fā)人員還需要基于Class.forName("xxx")的方式來裝載驅(qū)動,jdbc4也基于spi的機制來發(fā)現(xiàn)驅(qū)動提供商了震庭,可以通過META-INF/services/java.sql.Driver文件里指定實現(xiàn)類的方式來暴露驅(qū)動提供者.

SPI的使用

首先瑰抵,創(chuàng)建一個緩存數(shù)據(jù)源接口:

public interface CacheDataSource {

    String getDataSource();
}

接下來創(chuàng)建了兩個實現(xiàn)類:

public class DatabaseCacheDataSource implements CacheDataSource {
    @Override
    public String getDataSource() {
        System.out.println(this.getClass().getClassLoader());
        System.out.println("DatabaseCacheDataSource");
        return null;
    }
}

public class MemoryCacheDataSource implements CacheDataSource {
    @Override
    public String getDataSource() {
        System.out.println(this.getClass().getClassLoader());
        System.out.println("MemoryCacheDataSource");
        return null;
    }
}

最后我們在META-INF/services目錄下新建一個文件,名稱為接口的全限定名归薛,即包名加類名谍憔,內(nèi)容為實現(xiàn)類的全限定名。例如本例中的文件名稱為cn.ideabuffer.spi.CacheDataSource主籍,內(nèi)容如下:

cn.ideabuffer.spi.MemoryCacheDataSource

新建一個測試類:

public class Main {
    public static void main(String[] args) {
        ServiceLoader<CacheDataSource> s = ServiceLoader.load(CacheDataSource.class);
        Iterator<CacheDataSource> it = s.iterator();
        for (;it.hasNext();){
            CacheDataSource cacheDataSource = it.next();
            cacheDataSource.getDataSource();
        }
    }
}

這里通過ServiceLoader來獲取具體的實現(xiàn)類,執(zhí)行后的輸出如下:

sun.misc.Launcher$AppClassLoader@2503dbd3
MemoryCacheDataSource

我們查看一下ServiceLoader中的load方法:

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

這里使用了Context Class Loader逛球,為什么要這么使用呢千元,為什么不直接使用系統(tǒng)類加載器呢?接下來我們具體分析一下颤绕。

線程上下文類加載器

線程上下文類加載器(context class loader)是從JDK 1.2開始引入的幸海。類java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl) 用來獲取和設(shè)置線程的上下文類加載器。

如果沒有通過 setContextClassLoader(ClassLoader cl) 方法進行設(shè)置的話奥务,線程將繼承其父線程的上下文類加載器物独。Java 應(yīng)用運行的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運行的代碼可以通過此類加載器來加載類和資源氯葬。

為了加載類挡篓,Java還提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實現(xiàn)官研。那類加載就會存在問題:SPI 的接口是 Java 核心庫的一部分秽澳,是由引導類加載器來加載的;SPI 實現(xiàn)的 Java 類一般是由系統(tǒng)類加載器來加載的戏羽。引導類加載器是無法找到 SPI 的實現(xiàn)類的担神,因為它只加載 Java 的核心庫。在 SPI 接口的代碼中使用線程上下文類加載器始花,就可以成功的加載到 SPI 實現(xiàn)的類妄讯。java的雙親委托類加載機制(ClassLoader A -> System class loader -> Extension class loader -> Bootstrap class loader)可以保證核心類的正常安全加載。但是右邊的 Bootstrap class loader 所加載的代碼需要反過來去找委派鏈靠左邊的 ClassLoader A 去加載東西的時候酷宵,就需要委派鏈左邊的 ClassLoader 設(shè)置為線程的上下文加載器即可捞挥。

線程上下文類加載器的應(yīng)用

查看mysql-connector中的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!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

mysql-connector中的Driver實現(xiàn)了java.sql.Driver接口,那么查看mysql-connector中的META-INF/services目錄可以看到忧吟,有一個名為java.sql.Driver的文件砌函,文件的內(nèi)容為:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

下面看一下java.sql.DriverManager,在Driver的靜態(tài)代碼塊中將Driver注冊到了DriverManager中溜族,DriverManager依賴了Driver讹俊,但通過斷點查看,DriverManager是通過Bootstrap類加載器加載的煌抒,所以要加載Driver的實現(xiàn)類必須通過其他的類加載器仍劈,那么這時,Context ClassLoader的出現(xiàn)寡壮,解決了這一問題贩疙,也就是上文說過的ServiceLoader中的load方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末况既,一起剝皮案震驚了整個濱河市这溅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棒仍,老刑警劉巖悲靴,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異莫其,居然都是意外死亡癞尚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門乱陡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浇揩,“玉大人,你說我怎么就攤上這事憨颠「旎眨” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長膜廊。 經(jīng)常有香客問我乏沸,道長,這世上最難降的妖魔是什么爪瓜? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任蹬跃,我火速辦了婚禮,結(jié)果婚禮上铆铆,老公的妹妹穿的比我還像新娘蝶缀。我一直安慰自己,他們只是感情好薄货,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布翁都。 她就那樣靜靜地躺著,像睡著了一般谅猾。 火紅的嫁衣襯著肌膚如雪柄慰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天税娜,我揣著相機與錄音坐搔,去河邊找鬼。 笑死敬矩,一個胖子當著我的面吹牛概行,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弧岳,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼凳忙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了禽炬?” 一聲冷哼從身側(cè)響起涧卵,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞎抛,沒想到半個月后艺演,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡桐臊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了晓殊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片断凶。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖巫俺,靈堂內(nèi)的尸體忽然破棺而出认烁,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布却嗡,位于F島的核電站舶沛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏窗价。R本人自食惡果不足惜如庭,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撼港。 院中可真熱鬧坪它,春花似錦、人聲如沸帝牡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽靶溜。三九已至开瞭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罩息,已是汗流浹背嗤详。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扣汪,地道東北人断楷。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像崭别,于是被迫代替她去往敵國和親冬筒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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