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.sampled
和javax.sound.midi
包是提供給那些需要處理音頻工作的開(kāi)發(fā)者使用柒室,軟件包提供了信息獲取、控制和訪問(wèn)音頻相關(guān)接口逗宜。此外Java Sound API同樣提供了javax.sound.sampled.spi
和 javax.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)用AudioSystem
和MidiSystem
類下的方法,調(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è)方法getAudioFileFormat
和getAudioInputStream
)递礼。當(dāng)一個(gè)應(yīng)用程序試圖讀取音頻文件惨险,碰巧格式就是Acme時(shí),就會(huì)調(diào)用AudioSystem
類的方法來(lái)訪問(wèn)文件及其相關(guān)信息脊髓。AudioSystem.getAudioInputStream
和AudioSystem.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)