SPI的全名爲Service Provider Interface,簡單的總結下java spi機制的思想。咱們系統裏抽象的各個模塊,每每有不少不一樣的實現方案,好比日誌模塊的方案,xml解析模塊、jdbc模塊的方案等。面向的對象的設計裏,咱們通常推薦模塊之間基於接口編程,模塊之間不對實現類進行硬編碼。一旦代碼裏涉及具體的實現類,就違反了可拔插的原則,若是須要替換一種實現,就須要修改代碼。爲了實如今模塊裝配的時候能不在程序裏動態指明,這就須要一種服務發現機制。 java spi就是提供這樣的一個機制:爲某個接口尋找服務實現的機制java
上面摘抄了一下spi的概念,接着以我的的理解,簡單的談一下爲何會用到SPI, 什麼場景下能夠用到這個, 以及使用了SPI機制後有什麼優越性git
雖然最開始就引用了spi的解釋,這裏淺談一下我的理解。Service Provider Interface
以接口方式提供服務, 和API不一樣,spi的機制是定義一套標準規範的接口,實現交給其餘人來作。編程
因此一個接口,能夠有不少的實現,你徹底能夠根據本身的須要去選擇具體的實現方式,由於是面向接口的開發,因此你的業務代碼基本上就不用修改,就能夠切到另外一個實現了api
分別從框架層面和業務層面,給出一個我認爲比較合適的場景微信
SLF4j
SLF4j:大名鼎鼎的日誌輸出接口,這個jar包裏面提供的都只是接口方式,具體的實現須要本身去實現,固然比較經常使用的 logback
就是一個具體的實現包了, 在項目中使用 slf4j
的api進行日誌的輸出, 經過簡單的配置,引入logback, 就可使用logback來實現具體的日誌輸出; 也能夠換一個日誌實現 commons-logging
,業務上不須要任何的改動,就能夠用不一樣的實現來輸出日誌框架
假設你如今有個用戶註冊成功後的歡迎用戶的業務,不一樣渠道(微信,qq,微博等)註冊的,顯示的歡迎不一樣,對此有兩種不一樣的實現方式ide
結合上面的業務場景,來描述下能夠怎麼用測試
爲了實現代碼最大程度的複用,那麼能夠將不一樣的地方,抽象成一個SPI接口,在業務層經過接口來代替具體的實現類實現業務邏輯;ui
每一個渠道,都有個獨立的應用,那麼在微信渠道,建立一個 WeixinSpiImpl來實現接口編碼
在qq渠道,實現 QQSpiImpl;那麼在具體的接口調用處,實際上就是執行的spi實現類方法
這個與上面不一樣,同一個服務接口,根據不一樣的業務場景,選擇不一樣的實現來執行;固然你是徹底可使用 if, else來實現這種場景,惟一的問題就是擴展比較麻煩;
這種場景下,咱們但願的就是這個接口,能自動的根據業務場景,來選擇最合適的實現類來執行
簡單來說,就是 spi接口執行以前,其實須要有一個自動選擇匹配的實現類的前置過程;
一般這種業務場景下,具體的spi實現會有多個,可是須要有一個選擇的策略
在具體的實現以前,先定義一個小目標,咱們想要實現一個什麼樣子的東西出來
經過上面的背景描述,咱們的小目標也就很明確了,咱們的實現至少須要知足兩個場景
java自己就提供了一套spi的支持方式:
ServiceLoader
,咱們後續的開發,也會在這個基礎之上進行
利用java的 ServiceLoader
找到服務接口的實現類,有一些約定,下面給出要求說明和一個測試case
通常實現流程
IXxx
AXxx
, BXxx
META-INF/services/
目錄下新建一個文件,命名爲 spi接口的完整類名,內容爲spi接口實現的完整類名,一個實現類佔一行測試case以下
spi接口 com.hust.hui.quicksilver.commons.spi.HelloInterface
package com.hust.hui.quicksilver.commons.spi; /** * Created by yihui on 2017/3/17. */ public interface HelloInterface { void sayHello(); }
spi接口的兩個實現類
com.hust.hui.quicksilver.commons.spi.impl.ImageHello.java
package com.hust.hui.quicksilver.commons.spi.impl; import com.hust.hui.quicksilver.commons.spi.HelloInterface; /** * Created by yihui on 2017/3/17. */ public class ImageHello implements HelloInterface { @Override public void sayHello() { System.out.println("image hello!"); } }
com.hust.hui.quicksilver.commons.spi.impl.TextHello.java
package com.hust.hui.quicksilver.commons.spi.impl; import com.hust.hui.quicksilver.commons.spi.HelloInterface; /** * Created by yihui on 2017/3/17. */ public class TextHello implements HelloInterface { @Override public void sayHello() { System.out.println("text hello"); } }
配置文件 com.hust.hui.quicksilver.commons.spi.HelloInterface
com.hust.hui.quicksilver.commons.spi.impl.ImageHello com.hust.hui.quicksilver.commons.spi.impl.TextHello
測試類
public class HelloSpiTest { @Test public void testSPI() { ServiceLoader<HelloInterface> serviceLoader = ServiceLoader.load(HelloInterface.class); for (HelloInterface hello: serviceLoader) { hello.sayHello(); } } }
輸出以下:
image hello! text hello
測試類演示以下圖:
畫了一下結構圖,方便理解, 下面的核心是 SpiLoader
類, 負責加載spi接口的全部實現類, 初始化全部定義的選擇器, 返回一個spi接口的實現類初始化用戶自定義的spi對象,而後用戶持有此對象調用spi接口中提供的方法便可
博客系列連接:
源碼地址:
https://git.oschina.net/liuyueyi/quicksilver/tree/master/silver-spi