1 spi需要解決的問題剖析
首選看如下代碼:
@Test
public void demo1() {
JdbcService jdbcService = new JdbcServiceAImpl();
int i = jdbcService.insert("james");
if (i == 1) {
System.out.println("插入成功");
}
}
相信上面的代碼對於只要有過java開發經驗的人員來說,肯定都非常熟悉。
但是設想這樣一個場景,假設如果此時只有JdbcService接口,沒有其具體實現
,那我們能不能就非要拿到一個實現類(或者說一個符號),將上面的程序完成,等到你真有實現類了,我就直接去某個地方找到你的實現類,來調用你的方法呢?
其實這就是SPI所要解決的問題。
SPI 全稱 Service Provider Interface, 是 Java 提供的一套用來被第三方實現或
者擴展的 API, 它可以用來啓用框架擴展和替換組件。
可以看到, SPI 的本質, 其實是幫助程序, 爲某個特定的接口尋找它的實現類
。 而且哪些實現類的會加載, 是個動態過程(不是提前預定好的)。
有點類似 IOC 的思想, 就是將裝配的控制權移到程序之外, 在模塊化設計中這個機制尤其重要。 所以 SPI 的核心思想就是解耦。
比較常見的例子:
- 數據庫驅動加載接口實現類的加載
JDBC 加載不同類型數據庫的驅動 - 日 志門面接口實現類加載
SLF4J 加載不同提供商的日 志實現類 - Spring
Spring 中大量使用了 SPI, 比如: 對 servlet3. 0 規範對
ServletContainerInitializer 的實現、 自 動類型轉換 Type Conversion
SPI(Converter SPI、 Formatter SPI) 等
2 java spi使用簡介
2.1 使用java spi需要遵循的約定
要使用 Java SPI, 需要遵循如下約定:
- (1) 當服務提供者提供了接口的一種具體實現後, 在 jar 包的
META-INF/services
目 錄下創建一個以“接口全限定名”
爲命名的文件,內容爲實現類的全限定名
; - (2) 接口實現類所在的 jar 包放在主程序的 classpath 中;
- (3) 主程序通過 java. util. ServiceLoder 動態裝載實現模塊, 它通過掃描META-INF/services 目 錄下的配置文件找到實現類的全限定名, 把類加載到 JVM;
- (4)SPI 的實現類必須攜帶一個不帶參數的構造方法;
2.2 示例
(1)先定義一個接口
public interface JdbcService {
int insert(String name);
}
(2)再定義一系列它的具體實現
- 第一個實現
public class JdbcServiceAImpl implements JdbcService {
@Override
public int insert(String name) {
System.out.println(name + ",你好,調通了A實現!");
return 1;
}
}
- 第二個實現
public class JdbcServiceBImpl implements JdbcService {
@Override
public int insert(String name) {
System.out.println(name + ",你好,調通了B實現!");
return 1;
}
}
- 第三個實現
public class JdbcServiceCImpl implements JdbcService {
@Override
public int insert(String name) {
System.out.println(name + ",你好,調通了C實現!");
return 1;
}
}
等等
(3) 在 源碼的META-INF/services
目錄下創建一個以“接口全限定名”
爲命名的文件,內容爲實現類的全限定名
;
- 測試類
/**
* java spi機制驗證
*/
@Test
public void javaSPI() {
//服務加載器,加載實現類
ServiceLoader<JdbcService> serviceLoader = ServiceLoader.load(JdbcService.class);
//serviceLoader是實現了Iterable的迭代器,直接遍歷實現類
for (JdbcService service : serviceLoader) {
int james = service.insert("james");//依次調用實現類
System.out.println(james);
}
}
可以看到本測試類與本文剛開始時的代碼最明顯的不同就是,這裏並沒有出現任何接口的具體實現類。
也就是說ServiceLoader可以根據下面三個信息,生成具體的接口實現類。
- (1)接口的類型 — 這裏是JdbcService.class
- (2)在META-INF/services下以接口的全限定名爲名稱的文件
- (3)以及文件裏的實現類的全限定名
簡單看一下測試結果:
這篇文章讓我想起了《【springboot】— springboot的starter原理探究 + 如何自定義自己的starter》!!!
end!!!