Java SPI介紹使用

1.SPI的定義

A service provider interface(SPI) is the set of public interfaces and abstract classes that a service defines.
SPI是定義了一組公共的接口和抽象類的服務(wù)類鲸匿。其實(shí)就是定義了一組公共的接口或者抽象方法,例如Java Database Connectivity (JDBC) 規(guī)范阻肩,對(duì)于不同的數(shù)據(jù)庫(kù)廠商而言是SPI,但是對(duì)于使用JDBC的開(kāi)發(fā)者而言又是API运授。
SPI描述了服務(wù)廠商需要拓展實(shí)現(xiàn)的內(nèi)容規(guī)范烤惊,API則描述了如何、怎么使用的內(nèi)容吁朦。

2. 入門(mén)示例

javax.sound.sampledjavax.sound.midi包是提供給那些需要處理音頻工作的開(kāi)發(fā)者使用柒室,軟件包提供了信息獲取、控制和訪問(wèn)音頻相關(guān)接口逗宜。此外Java Sound API同樣提供了javax.sound.sampled.spijavax.sound.midi.spi相關(guān)軟件包雄右,其定義了音頻相關(guān)的抽象類空骚。開(kāi)發(fā)者想要提供新的音頻相關(guān)的服務(wù),只需要實(shí)現(xiàn)在SPI包下的具體類的子類即可擂仍。 這些類以及支持新服務(wù)的附加類囤屹,都被放到Jar文件中,并包含了對(duì)所附加了對(duì)一個(gè)或者多個(gè)服務(wù)的描述逢渔。當(dāng)這個(gè)Jar包被載入到用戶的CLASSPATH下肋坚,運(yùn)行時(shí)系統(tǒng)就會(huì)自動(dòng)的載入將新的服務(wù)類(擴(kuò)展了Java運(yùn)行時(shí)系統(tǒng)的功能)。一旦新的服務(wù)類被載入肃廓,它可以像其他已經(jīng)預(yù)先存在的類一樣被正常訪問(wèn)智厌。通過(guò)調(diào)用AudioSystemMidiSystem類下的方法,調(diào)用者可以獲取服務(wù)類的相關(guān)信息盲赊,也可以創(chuàng)建出服務(wù)類的實(shí)例铣鹏。應(yīng)用程序不需要也不應(yīng)該直接引用位于SPI包中的類來(lái)使用已經(jīng)載入的服務(wù)。

舉個(gè)栗子:現(xiàn)在有一個(gè)Acme軟件公司提供了新的服務(wù)類哀蘑,允許應(yīng)用程序讀取新格式的音頻文件诚卸。新的類就假設(shè)稱為AcmeAudioFileReader,其提供實(shí)現(xiàn)了AudioFileReader類的所有定義的方法(AudioFileReader僅有兩個(gè)方法getAudioFileFormatgetAudioInputStream)递礼。當(dāng)一個(gè)應(yīng)用程序試圖讀取音頻文件惨险,碰巧格式就是Acme時(shí),就會(huì)調(diào)用AudioSystem類的方法來(lái)訪問(wèn)文件及其相關(guān)信息脊髓。AudioSystem.getAudioInputStreamAudioSystem.getAudioFileFormat方法提供了一個(gè)讀取音頻流的標(biāo)準(zhǔn)API辫愉,在載入了AcmeAudioFileReader類的情況下,該接口被擴(kuò)展為透明地支持讀取新格式的音頻文件将硝。開(kāi)發(fā)者不需要直接訪問(wèn)新注冊(cè)的SPI類:通過(guò)AudioSystem對(duì)象的方法將參數(shù)傳遞給AcmeAudioFileReader恭朗。

例子中使用這些工廠類的目的是什么?為什么不讓?xiě)?yīng)用開(kāi)發(fā)者直接訪問(wèn)新的服務(wù)類依疼?這雖然是一種可行的途徑痰腮,但是通過(guò)統(tǒng)一的入口去管理和載入這些服務(wù)類,使得開(kāi)發(fā)人員不必知道每個(gè)載入的服務(wù)類是做什么的律罢。開(kāi)發(fā)者只需要通過(guò)統(tǒng)一的入口使用在這些服務(wù)類拿到想要的結(jié)果即可膀值,與此同時(shí)廠商也可以借助這種架構(gòu)有效的去管理包下可用資源。

通常使用新的音頻類對(duì)應(yīng)用程序是透明的误辑。 例如沧踏,想象一下,應(yīng)用程序開(kāi)發(fā)人員想要從文件中讀取音頻流的情況巾钉。 假設(shè)thePathName標(biāo)識(shí)一個(gè)音頻輸入文件翘狱,程序大概是這樣子的:

File theInFile = new File(thePathName);
AudioInputStream theInStream = AudioSystem.getAudioInputStream(theInFile); 

在這個(gè)場(chǎng)景下,AudioSystem檢測(cè)到哪些被載入的服務(wù)可以讀取這個(gè)文件砰苍,并要求對(duì)應(yīng)的服務(wù)類將音頻數(shù)據(jù)以一個(gè)AudioInputStream對(duì)象返回.開(kāi)發(fā)者可能并不知道甚至不在意輸入的音頻文件使用了新的第三方提供的格式潦匈。程序的第一次使用到的流對(duì)象是通過(guò)AudioSystem來(lái)完成阱高,而后續(xù)的所有的訪問(wèn)流和流屬性的都是通過(guò)AudioInputStream的方法來(lái)完成。這兩個(gè)都是javax.sound.sampled API的標(biāo)準(zhǔn)對(duì)象茬缩;所有可能的針對(duì)新文件格式的特殊處理的細(xì)節(jié)都已經(jīng)被完全的屏蔽了赤惊。

3. 窺探AudioSystem的秘密

上面例子中AudioSystem究竟有什么神奇的魔法,可以知道哪些被載入的服務(wù)類可以讀取這個(gè)文件寒屯,所謂源碼面前無(wú)秘密荐捻,直接"上菜"。

3.1 AudioSystem是如何找到對(duì)應(yīng)解析類

public static AudioInputStream getAudioInputStream(File file)
        throws UnsupportedAudioFileException, IOException {

        List providers = getAudioFileReaders();
        AudioInputStream audioStream = null;

        for(int i = 0; i < providers.size(); i++ ) {
            AudioFileReader reader = (AudioFileReader) providers.get(i);
            try {
                audioStream = reader.getAudioInputStream( file ); // throws IOException
                break;
            } catch (UnsupportedAudioFileException e) {
                continue;
            }
        }

        if( audioStream==null ) {
            throw new UnsupportedAudioFileException("could not get audio input stream from input file");
        } else {
            return audioStream;
        }
    }

首先通過(guò)getAudioFileReaders()方法獲取所有AudioFileReader第三方實(shí)現(xiàn)類的實(shí)例寡夹,然后循環(huán)遍歷AudioFileReader對(duì)象处面,如果當(dāng)前對(duì)象不支持當(dāng)前文件格式則拋出UnsupportedAudioFileException異常,捕獲異常繼續(xù)循環(huán)菩掏。神奇的魔法已經(jīng)被揭開(kāi):AudioSystem在這里其實(shí)隱式的約定魂角,第三方實(shí)現(xiàn)類如果不支持當(dāng)前文件格式的讀取則需要拋出特定的異常作為信號(hào)。

3.2 解析類是如何被載入的

通過(guò)追蹤getAudioFileReaders()智绸,追溯到如下關(guān)鍵代碼野揪,

static synchronized <T> List<T> getProviders(final Class<T> var0) {
        ArrayList var1 = new ArrayList(7);
        PrivilegedAction var2 = new PrivilegedAction<Iterator<T>>() {
            public Iterator<T> run() {
                return ServiceLoader.load(var0).iterator();
            }
        };
        final Iterator var3 = (Iterator)AccessController.doPrivileged(var2);
        PrivilegedAction var4 = new PrivilegedAction<Boolean>() {
            public Boolean run() {
                return var3.hasNext();
            }
        };

        while(((Boolean)AccessController.doPrivileged(var4)).booleanValue()) {
            try {
                Object var5 = var3.next();
                if (var0.isInstance(var5)) {
                    var1.add(0, var5);
                }
            } catch (Throwable var6) {
                ;
            }
        }

        return var1;
    }

同樣只需要關(guān)心主要代碼ServiceLoader.load(var0).iterator();,借助于ServiceLoader來(lái)完成的載入實(shí)例的工作瞧栗。

4. 使用Java的SPI機(jī)制

4.1 使用步驟

1. SPI定義
2. 統(tǒng)一訪問(wèn)入口
3. 實(shí)現(xiàn)SPI接口并加以描述實(shí)現(xiàn)了哪個(gè)SPI斯稳。(描述文件約定如下:放到META-INF/services/這個(gè)路      
   徑下;并以接口全路徑作為文件名稱迹恐,以實(shí)現(xiàn)接口類的全路徑作為文件內(nèi)容)

4.2 實(shí)踐

4.2.1 SPI定義:

public interface ObjectResourceStore<K ,V> {

    V read(K k) throws UnsupportedOperationException;

    void store(K k, V v) throws UnsupportedOperationException;

}

定義了一個(gè)具有儲(chǔ)取Object對(duì)象的功能的SPI挣惰,提供了read()store()方法。

4.2.2 統(tǒng)一訪問(wèn)入口

public final class ObjectResourceStores<K,V> {

    private static synchronized List<ObjectResourceStore> loaderProviders() {
        List<ObjectResourceStore> providers = new ArrayList();
        Iterator iterator = ServiceLoader.load(ObjectResourceStore.class).iterator();
        while (iterator.hasNext()) {
            providers.add((ObjectResourceStore) iterator.next());
        }
        return providers;
    }

    public static<K, V> V read(K k) throws UnsupportedOperationException {
        List<ObjectResourceStore> providers = loaderProviders();
        for (int i = 0; i < providers.size(); i++) {
            ObjectResourceStore provider = providers.get(i);
            try {
                return (V) provider.read(k);
            } catch (UnsupportedOperationException e) {
                continue;
            }
        }
        throw new UnsupportedOperationException("can not find suitable provider");
    }

    public static<K, V> void store(K k, V v) throws UnsupportedOperationException {
        List<ObjectResourceStore> providers = loaderProviders();
        for (int i = 0; i < providers.size(); i++) {
            ObjectResourceStore provider = providers.get(i);
            try {
                provider.store(k, v);
                return;
            } catch (UnsupportedOperationException e) {
                continue;
            }
        }
        throw new UnsupportedOperationException("can not find suitable provider");
    }
}

4.2.3 實(shí)現(xiàn)SPI

public class ObjectResourceMemoryStore implements ObjectResourceStore{

    private static ConcurrentHashMap store = new ConcurrentHashMap<Object, Object>();


    @Override
    public Object read(Object o) throws UnsupportedOperationException {
        return store.get(o);
    }

    @Override
    public void store(Object o, Object o2) throws UnsupportedOperationException {
        store.put(o, o2);
    }
}

創(chuàng)建描述文件


文件內(nèi)容如下:org.rhine.ObjectResourceMemoryStore

4.3 測(cè)試

測(cè)試代碼如下:

public class TestSPI {

    public static void main(String[] args) {

        ObjectResourceStores.store(1, 1);
        System.out.println(ObjectResourceStores.read(1).toString());
    }

}

程序能夠正確的輸出結(jié)果:1

至此一個(gè)完整的SPI的demo已經(jīng)完成殴边,大家如果有興趣可以繼續(xù)深入的研究下SPI在開(kāi)源框架中的具體實(shí)踐憎茂。比如Spring、Dubbo都有非常典型的應(yīng)用場(chǎng)景锤岸。

5. 寫(xiě)在最后

希望博客的內(nèi)容能給廣大的Java道友提供一些的幫助和提升竖幔。由于筆者水平有限,如果內(nèi)容有誤是偷,希望大家批評(píng)指出拳氢,索要源碼的同學(xué)可以直接私信。

參考文獻(xiàn)

  1. 官方SPI介紹
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛋铆,一起剝皮案震驚了整個(gè)濱河市饿幅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戒职,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件透乾,死亡現(xiàn)場(chǎng)離奇詭異洪燥,居然都是意外死亡磕秤,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)捧韵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)市咆,“玉大人,你說(shuō)我怎么就攤上這事再来∶衫迹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵芒篷,是天一觀的道長(zhǎng)搜变。 經(jīng)常有香客問(wèn)我,道長(zhǎng)针炉,這世上最難降的妖魔是什么挠他? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮篡帕,結(jié)果婚禮上殖侵,老公的妹妹穿的比我還像新娘。我一直安慰自己镰烧,他們只是感情好拢军,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著怔鳖,像睡著了一般茉唉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上败砂,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天赌渣,我揣著相機(jī)與錄音,去河邊找鬼昌犹。 笑死坚芜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斜姥。 我是一名探鬼主播鸿竖,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼铸敏!你這毒婦竟也來(lái)了缚忧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤杈笔,失蹤者是張志新(化名)和其女友劉穎闪水,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蒙具,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡球榆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年朽肥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片持钉。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衡招,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出每强,到底是詐尸還是另有隱情始腾,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布空执,位于F島的核電站浪箭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脆烟。R本人自食惡果不足惜山林,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望邢羔。 院中可真熱鬧驼抹,春花似錦、人聲如沸拜鹤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敏簿。三九已至明也,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惯裕,已是汗流浹背温数。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜻势,地道東北人撑刺。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像握玛,于是被迫代替她去往敵國(guó)和親够傍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理挠铲,服務(wù)發(fā)現(xiàn)冕屯,斷路器,智...
    卡卡羅2017閱讀 134,639評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法拂苹,類相關(guān)的語(yǔ)法安聘,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法搞挣,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,603評(píng)論 18 399
  • 從三月份找實(shí)習(xí)到現(xiàn)在带迟,面了一些公司,掛了不少囱桨,但最終還是拿到小米、百度嗅绰、阿里舍肠、京東、新浪窘面、CVTE翠语、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,218評(píng)論 11 349
  • 今年是90后參加的最后一屆高考,那些最小的90后也已經(jīng)開(kāi)始進(jìn)入大學(xué)的校門(mén)财边,更多的已經(jīng)開(kāi)始踏入社會(huì)肌括,從事著各...
    陽(yáng)仔觀天下閱讀 767評(píng)論 0 0
  • 版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載酣难。 一谍夭、算術(shù)運(yùn)算 C語(yǔ)言一共有34種運(yùn)算符,包括常見(jiàn)的加減乘除運(yùn)...
    LeaderBiao閱讀 581評(píng)論 0 1