1.JAVA 的SPI機(jī)制
SPI全稱(service provider interface)
,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制塞琼,目前市面上有很多框架都是用它來做服務(wù)的擴(kuò)展發(fā)現(xiàn),大家耳熟能詳?shù)娜鏙DBC、日志框架都有用到序芦;
簡單來說愧沟,它是一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制蔬咬。舉個(gè)簡單的例子,如果我們定義了一個(gè)規(guī)范央渣,需要第三方廠商去實(shí)現(xiàn)计盒,那么對(duì)于我們應(yīng)用方來說,只需要集成對(duì)應(yīng)廠商的插件芽丹,既可以完成對(duì)應(yīng)規(guī)范的實(shí)現(xiàn)機(jī)制北启。 形成一種插拔式的擴(kuò)展手段
。
2.實(shí)現(xiàn)一個(gè)SPI機(jī)制
- 下面是自己來模擬spi來加載數(shù)據(jù)庫驅(qū)動(dòng)的流程圖
數(shù)據(jù)庫的驅(qū)動(dòng)DataBaseDriver
/**
* @Project: 3.DistributedProject
* @description: 模擬數(shù)據(jù)庫的驅(qū)動(dòng)
* @author: sunkang
* @create: 2018-06-30 19:33
* @ModificationHistory who when What
**/
public interface DataBaseDriver {
String connect(String url);
}
MysqlDriver的驅(qū)動(dòng)
/**
* @Project: java-spi
* @description: 模擬MysqlDriver的驅(qū)動(dòng)
* @author: sunkang
* @create: 2018-06-30 19:34
* @ModificationHistory who when What
**/
public class MysqlDriver implements DataBaseDriver {
@Override
public String connect(String url) {
return "mySql driver : "+url;
}
}
- 在資源目錄下的META-INF/services/目錄下新建com.java.spi.DataBaseDriver的文件
com.java.spi.DataBaseDriver表示為接口的名稱蚊俺,com.java.spi.MysqlDriver為驅(qū)動(dòng)的具體實(shí)現(xiàn)類
com.java.spi.MysqlDriver
spi應(yīng)用的啟動(dòng)的例子
/**
* @Project: java-spi
* @description: 應(yīng)用的啟動(dòng)的例子
* @author: sunkang
* @create: 2018-06-30 19:33
* @ModificationHistory who when What
**/
public class JavaSpiDemo
{
public static void main( String[] args )
{
ServiceLoader<DataBaseDriver> serviceLoader =ServiceLoader.load(DataBaseDriver.class);
for(DataBaseDriver dataBaseDriver :serviceLoader){
System.out.println(dataBaseDriver.connect("com.sunkang "));
}
}
}
- 輸出結(jié)果為:
mySql driver : com.sunkang
3.SPI規(guī)范總結(jié)
實(shí)現(xiàn)SPI,就需要按照SPI本身定義的規(guī)范來進(jìn)行配置得封,SPI規(guī)范如下
1.需要在classpath下創(chuàng)建一個(gè)目錄,該目錄命名必須是:META-INF/services
2.在該目錄下創(chuàng)建一個(gè)文件拷呆,該文件需要滿足以下幾個(gè)條件
文件名必須是擴(kuò)展的接口的全路徑名稱
文件內(nèi)部描述的是該擴(kuò)展接口的所有實(shí)現(xiàn)類
文件的編碼格式是UTF-8
3.通過java.util.ServiceLoader的加載機(jī)制來發(fā)現(xiàn)
4.SPI的實(shí)際應(yīng)用
SPI在很多地方有應(yīng)用,大家可以看看最常用的java.sql.Driver驅(qū)動(dòng)腰懂。JDK官方提供了java.sql.Driver這個(gè)驅(qū)動(dòng)擴(kuò)展點(diǎn)绣溜,但是你們并沒有看到JDK中有對(duì)應(yīng)的Driver實(shí)現(xiàn)涮毫。 那在哪里實(shí)現(xiàn)呢?
以連接Mysql為例咒吐,我們需要添加mysql-connector-java依賴属划。然后恬叹,你們可以在這個(gè)jar包中找到SPI的配置信息。如下圖,所以java.sql.Driver由各個(gè)數(shù)據(jù)庫廠商自行實(shí)現(xiàn)。這就是SPI的實(shí)際應(yīng)用。當(dāng)然除了這個(gè)意外的妖,大家在spring的包中也可以看到相應(yīng)的痕跡
5.SPI的缺點(diǎn)
1.JDK標(biāo)準(zhǔn)的SPI會(huì)一次性加載實(shí)例化擴(kuò)展點(diǎn)的所有實(shí)現(xiàn)
,什么意思呢忘蟹?就是如果你在META-INF/service下的文件里面加了N個(gè)實(shí)現(xiàn)類狠毯,那么JDK啟動(dòng)的時(shí)候都會(huì)一次性全部加載。那么如果有的擴(kuò)展點(diǎn)實(shí)現(xiàn)初始化很耗時(shí)或者如果有些實(shí)現(xiàn)類并沒有用到,那么會(huì)很浪費(fèi)資源
2.如果擴(kuò)展點(diǎn)加載失敗,會(huì)導(dǎo)致調(diào)用方報(bào)錯(cuò)颜及,而且這個(gè)錯(cuò)誤很難定位到是這個(gè)原因
6.ServiceLoader類的分析
ServiceLoader<DataBaseDriver> serviceLoader =ServiceLoader.load(DataBaseDriver.class);
for(DataBaseDriver dataBaseDriver :serviceLoader){
System.out.println(dataBaseDriver.connect("com.sunkang "));
}
public final class ServiceLoader<S> implements Iterable<S>
該ServiceLoader實(shí)現(xiàn)了Iterable的接口墨林,具體了迭代器的功也就可以進(jìn)行for循環(huán)了
該load的方法會(huì)會(huì)初始化LazyIterator的迭代器
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
那么的對(duì)應(yīng)引入的驅(qū)動(dòng)是如何發(fā)現(xiàn)并實(shí)例化的,可以猜想:
先通過掃描下 META-INF/services+接口名反浓,如果資源存在萌丈,則讀取具體的內(nèi)容進(jìn)行反射實(shí)例化
對(duì)應(yīng)的源碼這里就不解析了,的確也是這樣做的雷则。