Java SPI機制

前言

雖然我自己在前段時間再總結(jié)一些Java知識寻拂,但是經(jīng)過最近的面試發(fā)現(xiàn)晨川,很多自己掌握的并不牢靠刷喜,所以決定把原來很多內(nèi)容拆分出來一部分一部分自己寫荣回,這篇主要在梳理一遍Java的SPI 機制吧侄榴。溫故而知新雹锣,可以為師矣。


介紹

Java SPI 全程為 Service Provider Interface癞蚕,直譯過來就是 服務(wù)提供商接口蕊爵。我理解的概念的話就是,由JDK語言開發(fā)組制定一系列功能接口桦山,但功能的具體實現(xiàn)是由各個服務(wù)商自行提供攒射。這也滿足的依賴倒置原則。依賴倒置原則的核心就是要我們面向接口編程,理解了面向接口編程恒水。

具體事例

最熟悉的SPI服務(wù)應(yīng)該就是JDBC了

在java.util.sql中定義了對于數(shù)據(jù)庫功能的各個接口会放,以及數(shù)據(jù)庫操作生命周期中各對象的接口

如Driver表示數(shù)據(jù)庫的驅(qū)動器,Connection表示一次數(shù)據(jù)庫的連接

我們在使用第三方實現(xiàn)的時候我們一般是直接通過java.util.sql中的DriverManager來獲取具體的第三方Driver或者Connection,那么DriverManager是如何加載到第三方數(shù)據(jù)庫的呢钉凌?

接下來我們就好好梳理一下從接口Class文件加載到第三方實現(xiàn)的加載咧最,以及第三方實現(xiàn)的調(diào)用的完整流程,看一下DriverManager的源碼

環(huán)境

jdk1.8.0_144

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

加載SPI第三方實現(xiàn)

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

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

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
        //.... 此處省略一些不重要的內(nèi)容

        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
         //關(guān)鍵是這里的Class.forName(aDriver,true,ClassLoader.getSystemClassLoader())
         //此處使用ClassLoader.getSystemClassLoader()直接使用了AppClassLoader來加載具體實現(xiàn)類,打破了雙親委派機制
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

再看一下第三方的Driver實現(xiàn)類中都干了些什么,此處以com.mysql.jdbc的Driver為例

    static {
        try {
        //將自己的Driver實例注冊進java.util.sql.DriverManager的CopyOnWriteArrayList列表中御雕,供后期使用
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

當SPI接口調(diào)用第三方的功能實現(xiàn)矢沿,此處以JDBC的getConnection為例

       try {
            DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","test","123456");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    @CallerSensitive
    public static Connection getConnection(String url)
        throws SQLException {

        java.util.Properties info = new java.util.Properties();
        //此處返回的是直接調(diào)用DriverManager的類,一般為我們自己的程序類
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        
        //此處為我們自己程序類的類加載器 一般為AppClassLoader
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        //此處省略一些不重要的內(nèi)容

        for(DriverInfo aDriver : registeredDrivers) {
            
            //由AppClassLoader檢查是否已經(jīng)裝載了第三方驅(qū)動類
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                   
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }
        //此處省略一些不重要的內(nèi)容
       
    }

總結(jié)

到目前為止我們已經(jīng)弄清楚饮笛,由BootstrapClassloader加載的協(xié)議接口類咨察,如何打破雙親委派機制 來加載AppClassLoader才可以加載到的classpath中的第三方實現(xiàn)類

主要方式為

  1. 使用ClassLoader.getSystemClassLoader來獲取AppClassLoader
      Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
  1. 雖然實際使用中沒有用到论熙,使用Thread.currentThread().getContextClassLoader()獲取AppClassLoader
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末福青,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌无午,老刑警劉巖媒役,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宪迟,居然都是意外死亡酣衷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門次泽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穿仪,“玉大人,你說我怎么就攤上這事意荤“∑” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵玖像,是天一觀的道長紫谷。 經(jīng)常有香客問我,道長捐寥,這世上最難降的妖魔是什么笤昨? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮握恳,結(jié)果婚禮上瞒窒,老公的妹妹穿的比我還像新娘。我一直安慰自己乡洼,他們只是感情好根竿,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著就珠,像睡著了一般寇壳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妻怎,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天壳炎,我揣著相機與錄音,去河邊找鬼逼侦。 笑死匿辩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的榛丢。 我是一名探鬼主播铲球,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晰赞!你這毒婦竟也來了稼病?” 一聲冷哼從身側(cè)響起选侨,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎然走,沒想到半個月后援制,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡芍瑞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年晨仑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拆檬。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡洪己,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竟贯,到底是詐尸還是另有隱情码泛,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布澄耍,位于F島的核電站噪珊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏齐莲。R本人自食惡果不足惜痢站,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望选酗。 院中可真熱鬧阵难,春花似錦、人聲如沸芒填。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殿衰。三九已至朱庆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闷祥,已是汗流浹背娱颊。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凯砍,地道東北人箱硕。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像悟衩,于是被迫代替她去往敵國和親剧罩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

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

  • 主要回顧了java的類加載機制幕与,servlet3.0新特性,java的spi機制舰罚,以及spring-mvc的初始化...
    進擊de大黃閱讀 7,525評論 0 18
  • 本文以JDBC為例深入講解 java spi 機制,將幫助你理解:什么是SPI薛耻,SPI實現(xiàn)原理营罢,SPI的使用和SP...
    匠丶閱讀 5,231評論 0 8
  • 概述 ??在某些時候我們可以通過在軟件上游提供服務(wù)接口,無需在意接口的實現(xiàn)邏輯饼齿,全部交由擴展程序進行實現(xiàn)饲漾,上游只需...
    ruoshy閱讀 617評論 0 6
  • 月考考完了,老師把語文試卷發(fā)了下來缕溉,看到了分數(shù)考传,我考的很不理想。 第二大題证鸥,看拼音僚楞,寫詞語,化妝...
    俊浩閱讀 1,097評論 0 1
  • 我們上學(xué)的時候枉层,一下課泉褐,跑到操場上,跳跳格子鸟蜡,踢踢毽子膜赃。上課鈴聲響起,小手拍著嘴“喔喔喔”表示暫停揉忘。下了這節(jié)課出來...
    合百合閱讀 786評論 11 18