spi 02-spi 的實戰解決 slf4j 包衝突問題java
spi 04-spi dubbo 實現源碼解析github
spi 05-dubbo adaptive extension 自適應拓展sql
spi 06-本身從零手寫實現 SPI 框架apache
之前一直想指定一套標準,讓別人按照這個標準來實現,並編寫好對應的容器。oracle
而後我在代碼中動態獲取這些實現,讓代碼運行起來。框架
如何獲取某個接口的實現?maven
和同事討論,是經過掃描包的 class 的方式。而後判斷是否爲定製標準的子類。
以爲很彆扭,須要限定死實現類的包名稱,並且性能也較差。
今天在閱讀 hibernate-validator 源碼時受到了啓發。
能夠經過 SPI 的方式,更加天然的解決這個問題。
SPI 是 Service Provider Interfaces 的縮寫。
本文簡單介紹下如何使用,具體原理,暫時不作深究。
SPI 是 Java 提供的一種服務加載方式,全名爲 Service Provider Interface。
根據 Java 的 SPI 規範,咱們能夠定義一個服務接口,具體的實現由對應的實現者去提供,即服務提供者。
而後在使用的時候再根據 SPI 的規範去獲取對應的服務提供者的服務實現。
經過 SPI 服務加載機制進行服務的註冊和發現,能夠有效的避免在代碼中將具體的服務提供者寫死。從而能夠基於接口編程,實現模塊間的解耦。
在 META-INF/services/ 目錄中建立以接口全限定名命名的文件,該文件內容爲API具體實現類的全限定名
使用 ServiceLoader 類動態加載 META-INF 中的實現類
如 SPI 的實現類爲 Jar 則須要放在主程序 ClassPath 中
SPI 應用場景舉例
jdbc4.0之前, 開發人員還須要基於Class.forName("xxx")的方式來裝載驅動,jdbc4也基於spi的機制來發現驅動提供商了,能夠經過METAINF/services/java.sql.Driver文件裏指定實現類的方式來暴露驅動提供者.
apache最先提供的日誌的門面接口。只有接口,沒有實現。
具體方案由各提供商實現,發現日誌提供商是經過掃描METAINF/services/org.apache.commons.logging.LogFactory配置文件,經過讀取該文件的內容找到日誌提工商實現類。
只要咱們的日誌實現裏包含了這個文件,並在文件裏制定 LogFactory 工廠接口的實現類便可。
. ├── java │ └── com │ └── github │ └── houbb │ └── forname │ ├── Say.java │ ├── Sing.java │ └── impl │ ├── DefaultSay.java │ └── DefaultSing.java └── resources └── META-INF └── services └── com.github.houbb.forname.Say
public interface Say { /** * 說 */ void say(); }
import com.github.houbb.forname.Say; public class DefaultSay implements Say { @Override public void say() { System.out.println("Default say"); } }
在 resources 目錄下,建立 META-INF/services 文件夾,以接口全路徑名 com.github.houbb.forname.Say 爲文件名稱,內容爲對應的實現類全路徑。
若是是多個,就直接換行隔開。
com.github.houbb.forname.impl.DefaultSay
public class SayTest { @Test public void spiTest() { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ServiceLoader<Say> loader = ServiceLoader.load(Say.class, classLoader); for (Say say : loader) { say.say(); } } }
Default say
Java 中,能夠經過 ServiceLoader 類比較方便的找到該類的全部子類實現。
META-INF/services 下的實現指定和實現子類實現徹底能夠和接口定義徹底分開。
每次都要手動建立實現指定文件,比較繁瑣。
Auto 就爲解決這個問題而生。
<dependencies> <dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> <version>1.0-rc4</version> <optional>true</optional> </dependency> </dependencies>
public interface Sing { /** * 唱歌 */ void sing(); }
@AutoService(Sing.class) public class DefaultSing implements Sing { @Override public void sing() { System.out.println("Sing a song..."); } }
public class SingTest { @Test public void spiTest() { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ServiceLoader<Sing> loader = ServiceLoader.load(Sing.class, classLoader); for (Sing sing : loader) { sing.sing(); } } }
Sing a song...
經過 google 的 auto,能夠在編譯時自動爲咱們生成對應的接口實現指定文件。
在 target 對應的文件下能夠看到。
實現原理,也相對簡單。經過 java 的編譯時註解,生成對應的文件便可。
實際上諸如 dubbo 等框架,會利用 SPI 機制來提高項目總體的靈活性。
java 自帶的 SPI 有不少不足的地方,本系列就是要學習使用,而且實現本身加強的 SPI 框架。