Java - SPI

SPI簡(jiǎn)介

SPI巡验,Service Provider Interface珍特,主要是被框架的開(kāi)發(fā)人員使用,比如java.sql.Driver接口鹏秋,其他不同廠商可以針對(duì)同一接口做出不同的實(shí)現(xiàn)干毅,mysql和postgresql都有不同的實(shí)現(xiàn)提供給用戶,而Java的SPI機(jī)制可以為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)岸浑。

當(dāng)服務(wù)的提供者提供了一種接口的實(shí)現(xiàn)之后,需要在classpath下的META-INF/services/目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件瑰步,這個(gè)文件里的內(nèi)容就是這個(gè)接口的具體的實(shí)現(xiàn)類矢洲。當(dāng)其他的程序需要這個(gè)服務(wù)的時(shí)候,就可以通過(guò)查找這個(gè)jar包(一般都是以jar包做依賴)的META-INF/services/中的配置文件缩焦,配置文件中有接口的具體實(shí)現(xiàn)類名读虏,可以根據(jù)這個(gè)類名進(jìn)行加載實(shí)例化,就可以使用該服務(wù)了袁滥。JDK中查找服務(wù)的實(shí)現(xiàn)的工具類是:java.util.ServiceLoader盖桥。

SPI可以認(rèn)為是一個(gè)規(guī)范,按照此規(guī)范實(shí)施题翻,就能被服務(wù)正確識(shí)別自定義的功能揩徊。

Dubbo,

如何使用SPI

使用SPI的大致流程如下:

  • 有關(guān)組織或者公司定義標(biāo)準(zhǔn)。
  • 具體廠商或者框架開(kāi)發(fā)者實(shí)現(xiàn)嵌赠。
  • 程序猿使用塑荒。

詳細(xì)來(lái)講,就是:
標(biāo)準(zhǔn)制定者的工作

  • 使用ServiceLoader.load(Class class); 動(dòng)態(tài)加載Service接口的實(shí)現(xiàn)類姜挺。

具體實(shí)施者的工作

  • 在META-INF/services/目錄中創(chuàng)建以Service接口全限定名命名的文件齿税,該文件內(nèi)容為Service接口具體實(shí)現(xiàn)類的全限定名,文件編碼必須為UTF-8炊豪。
  • 如SPI的實(shí)現(xiàn)類為jar凌箕,則需要將其放在當(dāng)前程序的classpath下。
  • Service的具體實(shí)現(xiàn)類必須有一個(gè)不帶參數(shù)的構(gòu)造方法词渤。

應(yīng)用舉例

這里以JDBC為例

在JDBC4.0之前牵舱,我們開(kāi)發(fā)有連接數(shù)據(jù)庫(kù)的時(shí)候,通常會(huì)用Class.forName("com.mysql.jdbc.Driver")這句先加載數(shù)據(jù)庫(kù)相關(guān)的驅(qū)動(dòng)掖肋,然后再進(jìn)行獲取連接等的操作仆葡。而JDBC4.0之后不需要用Class.forName("com.mysql.jdbc.Driver")來(lái)加載驅(qū)動(dòng),直接使用DriverManager.getConnection(String url)就可以了。

注:

在以前沿盅,操作數(shù)據(jù)庫(kù)的時(shí)候把篓,需要先加載驅(qū)動(dòng)。不同的數(shù)據(jù)庫(kù)需要不同的驅(qū)動(dòng)腰涧。如:

  • mysql - Class.forName("com.mysql.jdbc.Driver").newInstance()
  • oracle - Class.forName("oracle.jdbc.driver.OracleDriver").newInstance()
  • sql server - Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance()
  • postgreSQL - Class.forNaem("org.postgresql.Driver").newInstance()
  • db2 - Class.froName("com.ibm.db2.jdbc.app.DB2Driver").newInstance()

如果要更改數(shù)據(jù)庫(kù)韧掩,就需要更改驅(qū)動(dòng),也就是說(shuō)要修改代碼窖铡。修改代碼是個(gè)很麻煩的工作疗锐。使用SPI可以使我們無(wú)需修改代碼就可以更換數(shù)據(jù)庫(kù)。

通過(guò)SPI是如何實(shí)現(xiàn)的呢费彼?

1. 組織方制定接口

這里的組織方是JDK組織滑臊。JDK中有定義驅(qū)動(dòng)接口Driver:

public interface Driver{
    //接口方法略。箍铲。雇卷。
}

2. 實(shí)現(xiàn)方根據(jù)SPI規(guī)范實(shí)現(xiàn)接口

這里的實(shí)現(xiàn)方是各數(shù)據(jù)庫(kù)廠商

msyql
對(duì)于MySQL,在mysql-connector-java.jar下颠猴,有META-INF/services目錄关划,該目錄下有SPI需要的文件java.sql.Driver:

image.png

文件內(nèi)容為:

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

postgreSQL
同理,對(duì)于postgreSQL翘瓮,在postgresql.jar中贮折,META-INF/services目錄下也有名為java.sql.Driver的文件:

image.png

文件內(nèi)容為:

org.postgresql.Driver

其他數(shù)據(jù)庫(kù)與之類似。具體接口實(shí)現(xiàn)略過(guò)资盅。

3. 組織方加載實(shí)現(xiàn)類

各個(gè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)的加載是在JDK的DriverManager類中實(shí)現(xiàn)的调榄。該類中有如下一段靜態(tài)代碼,會(huì)在類初始化的時(shí)候執(zhí)行:

class DriverManager{
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}

loadInitialDrivers()函數(shù)的內(nèi)容為:

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();
             try{
                 while(driversIterator.hasNext()) {
                     driversIterator.next();
                 }
             } catch(Throwable t) {
             // Do nothing
             }
             return null;
         }
     });
    //下面代碼略律姨。振峻。。
}

這段代碼中择份,ServiceLoader.load()會(huì)查找classpath以及jar包中扣孟,META-INF/services目錄下的所有java.sql.Driver的實(shí)現(xiàn)類,并實(shí)例化荣赶。

使用SPI的第一步操作如下凤价。這里沒(méi)有去META-INF/services目錄下查找配置文件,也沒(méi)有加載具體實(shí)現(xiàn)類拔创,做的事情就是封裝了我們的接口類型和類加載器利诺,并初始化了一個(gè)迭代器。

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

第二步操作為:遍歷使用SPI獲取到的具體實(shí)現(xiàn)剩燥,實(shí)例化各個(gè)實(shí)現(xiàn)類

//獲取迭代器
Iterator<Driver> driversIterator = loadedDrivers.iterator();
//遍歷所有的驅(qū)動(dòng)實(shí)現(xiàn)
while(driversIterator.hasNext()) {
    driversIterator.next();
}

在遍歷的時(shí)候慢逾,首先調(diào)用driversIterator.hasNext()方法立倍,這里會(huì)搜索classpath下以及jar包中所有的META-INF/services目錄下的java.sql.Driver文件,并找到文件中的實(shí)現(xiàn)類的名字侣滩,此時(shí)并沒(méi)有實(shí)例化具體的實(shí)現(xiàn)類口注。

第三步為:實(shí)例化各個(gè)驅(qū)動(dòng)。關(guān)鍵代碼為:

driversIterator.next();

到此為止君珠,classpath下的所有數(shù)據(jù)庫(kù)驅(qū)動(dòng)都被加載并實(shí)例化了寝志。

在測(cè)試項(xiàng)目中添加了兩個(gè)jar包,mysql-connector-java-6.0.6.jar和postgresql-42.0.0.0.jar策添,跟蹤到DriverManager中之后:


image.png

可以看到此時(shí)迭代器中有兩個(gè)驅(qū)動(dòng)材部,mysql和postgresql的都被加載了。有關(guān)兩個(gè)驅(qū)動(dòng)都加載了唯竹,具體使用哪個(gè)驅(qū)動(dòng)乐导,請(qǐng)自行深入jdbc的源碼。這里不做過(guò)多解析摩窃。

誰(shuí)使用了SPI

有很多的SPI擴(kuò)展機(jī)制應(yīng)用的實(shí)例兽叮,比如大名鼎鼎的slf4j, common-logging, JDBC, tomcat等等

SPI的思想

SPI重要的不是其原理,而是其思想猾愿。
Dubbo就借鑒了SPI的思想,實(shí)現(xiàn)了自動(dòng)擴(kuò)展账阻。詳情可參考下面這篇文章蒂秘。
Dubbo中SPI擴(kuò)展機(jī)制詳解

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市淘太,隨后出現(xiàn)的幾起案子姻僧,更是在濱河造成了極大的恐慌,老刑警劉巖蒲牧,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撇贺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡冰抢,警方通過(guò)查閱死者的電腦和手機(jī)松嘶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挎扰,“玉大人翠订,你說(shuō)我怎么就攤上這事∽窬耄” “怎么了尽超?”我有些...
    開(kāi)封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)梧躺。 經(jīng)常有香客問(wèn)我似谁,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任巩踏,我火速辦了婚禮斜筐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛀缝。我一直安慰自己顷链,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布屈梁。 她就那樣靜靜地躺著嗤练,像睡著了一般。 火紅的嫁衣襯著肌膚如雪在讶。 梳的紋絲不亂的頭發(fā)上煞抬,一...
    開(kāi)封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音构哺,去河邊找鬼革答。 笑死,一個(gè)胖子當(dāng)著我的面吹牛曙强,可吹牛的內(nèi)容都是我干的残拐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼碟嘴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼溪食!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起娜扇,我...
    開(kāi)封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤错沃,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后雀瓢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體枢析,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年刃麸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了醒叁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嫌蚤,死狀恐怖辐益,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脱吱,我是刑警寧澤智政,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站箱蝠,受9級(jí)特大地震影響续捂,放射性物質(zhì)發(fā)生泄漏垦垂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一牙瓢、第九天 我趴在偏房一處隱蔽的房頂上張望劫拗。 院中可真熱鬧,春花似錦矾克、人聲如沸页慷。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酒繁。三九已至,卻和暖如春控妻,著一層夾襖步出監(jiān)牢的瞬間州袒,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工弓候, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留郎哭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓菇存,卻偏偏與公主長(zhǎng)得像夸研,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撰筷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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