Java 程式員在日常工作中經常會聽到 SPI,而且很多框架都使用了 SPI 的技術,那麼問題來了,到底什麼是 SPI 呢?今天阿粉就帶大家好好瞭解一下 SPI。 SPI 概念 SPI 全稱是 Service Provider Interface,是一種 JDK 內置的動態載入實現擴展點的機制,通過 ...
Java
程式員在日常工作中經常會聽到 SPI
,而且很多框架都使用了 SPI
的技術,那麼問題來了,到底什麼是 SPI
呢?今天阿粉就帶大家好好瞭解一下 SPI。
SPI 概念
SPI
全稱是 Service Provider Interface
,是一種 JDK
內置的動態載入實現擴展點的機制,通過 SPI
技術我們可以動態獲取介面的實現類,不用自己來創建。
這裡提到了介面和實現類,那麼 SPI
技術上具體有哪些技術細節呢?
- 介面:需要有一個功能介面;
- 實現類:介面只是規範,具體的執行需要有實現類才行,所以不可缺少的需要有實現類;
- 配置文件:要實現
SPI
機制,必須有一個與介面同名的文件存放於類路徑下麵的META-INF/services
文件夾中,並且文件中的每一行的內容都是一個實現類的全路徑; - 類載入器
ServiceLoader
:JDK
內置的一個類載入器,用於載入配置文件中的實現類;
舉個慄子
上面說了 SPI
的幾個概念,接下來阿粉就通過一個慄子來帶大家感受一下具體的用法。
第一步
創建一個介面,這裡我們創建一個解壓縮的介面,其中定義了壓縮和解壓的兩個方法。
package com.example.demo.spi;
/**
* <br>
* <b>Function:</b><br>
* <b>Author:</b>@author ziyou<br>
* <b>Date:</b>2022-10-08 21:31<br>
* <b>Desc:</b>無<br>
*/
public interface Compresser {
byte[] compress(byte[] bytes);
byte[] decompress(byte[] bytes);
}
第二步
再寫兩個對應的實現類,分別是 GzipCompresser.java
和 WinRarCompresser.java
代碼如下
package com.example.demo.spi.impl;
import com.example.demo.spi.Compresser;
import java.nio.charset.StandardCharsets;
/**
* <br>
* <b>Function:</b><br>
* <b>Author:</b>@author ziyou<br>
* <b>Date:</b>2022-10-08 21:33<br>
* <b>Desc:</b>無<br>
*/
public class GzipCompresser implements Compresser {
@Override
public byte[] compress(byte[] bytes) {
return"compress by Gzip".getBytes(StandardCharsets.UTF_8);
}
@Override
public byte[] decompress(byte[] bytes) {
return "decompress by Gzip".getBytes(StandardCharsets.UTF_8);
}
}
package com.example.demo.spi.impl;
import com.example.demo.spi.Compresser;
import java.nio.charset.StandardCharsets;
/**
* <br>
* <b>Function:</b><br>
* <b>Author:</b>@author ziyou<br>
* <b>Date:</b>2022-10-08 21:33<br>
* <b>Desc:</b>無<br>
*/
public class WinRarCompresser implements Compresser {
@Override
public byte[] compress(byte[] bytes) {
return "compress by WinRar".getBytes(StandardCharsets.UTF_8);
}
@Override
public byte[] decompress(byte[] bytes) {
return "decompress by WinRar".getBytes(StandardCharsets.UTF_8);
}
}
第三步
創建配置文件,我們接著在 resources
目錄下創建一個名為 META-INF/services
的文件夾,在其中創建一個名為 com.example.demo.spi.Compresser
的文件,其中的內容如下:
com.example.demo.spi.impl.WinRarCompresser
com.example.demo.spi.impl.GzipCompresser
註意該文件的名稱必須是介面的全路徑,文件裡面的內容每一行都是一個實現類的全路徑,多個實現類就寫在多行裡面,效果如下。
第四步
有了上面的介面,實現類和配置文件,接下來我們就可以使用 ServiceLoader
動態載入實現類,來實現 SPI
技術了,如下所示:
package com.example.demo;
import com.example.demo.spi.Compresser;
import java.nio.charset.StandardCharsets;
import java.util.ServiceLoader;
public class TestSPI {
public static void main(String[] args) {
ServiceLoader<Compresser> compressers = ServiceLoader.load(Compresser.class);
for (Compresser compresser : compressers) {
System.out.println(compresser.getClass());
}
}
}
運行的結果如下
可以看到我們正常的獲取到了介面的實現類,並且可以直接使用實現類的解壓縮方法。
原理
知道瞭如何使用 SPI
接下來我們來研究一下是如何實現的,通過上面的測試我們可以看到,核心的邏輯是 ServiceLoader.load()
方法,這個方法有點類似於 Spring
中的根據介面獲取所有實現類一樣。
點開 ServiceLoader
我們可以看到有一個常量 PREFIX
,如下所示,這也是為什麼我們必須在這個路徑下麵創建配置文件,因為 JDK
代碼裡面會從這個路徑裡面去讀取我們的文件。
同時又因為在讀取文件的時候使用了 class
的路徑名稱,因為我們使用 load
方法的時候只會傳遞一個 class
,所以我們的文件名也必須是介面的全路徑。
通過 load
方法我們可以看到底層構造了一個 java.util.ServiceLoader.LazyIterator
迭代器。
在迭代器中的 parse
方法中,就獲取了配置文件中的實現類名稱集合,然後在通過反射創建出具體的實現類對象存放到 LinkedHashMap<String,S> providers = new LinkedHashMap<>();
中。
常用的框架
SPI 技術的使用非常廣泛,比如在 Dubble
,不過 Dubble
中的 SPI
有經過改造的,還有我們很常見的資料庫的驅動中也使用了 SPI
,感興趣的小伙伴可以去翻翻看,還有 SLF4J
用來載入不同提供商的日誌實現類以及 Spring
框架等。
優缺點
前面介紹了 SPI
的原理和使用,那 SPI
有什麼優缺點呢?
優點
優點當然是解耦,服務方只要定義好介面規範就好了,具體的實現可以由不同的 Jar
進行實現,只要按照規範實現功能就可以被直接拿來使用,在某些場合會被進行熱插拔使用,實現瞭解耦的功能。
缺點
一個很明顯的缺點那就是做不到按需載入,通過源碼我們看到了是會將所有的實現類都進行創建的,這種做法會降低性能,如果某些實現類實現很耗時了話將影響載入時間。同時實現類的命名也沒有規範,讓使用者不方便引用。
總結
阿粉今天給大家介紹了一個 SPI
的原理和實現,感興趣的小伙伴可以自己去嘗試一下,多動手有利於加深記憶哦,如果覺得我們的文章有幫助,歡迎點贊評論分享轉發,讓更多的人看到。
更多優質內容歡迎關註公眾號【Java 極客技術】,我準備了一份面試資料,回覆【bbbb07】免費領取。希望能在這寒冷的日子里,幫助到大家。
本文來自博客園,作者:zi-you,轉載請註明原文鏈接:https://www.cnblogs.com/zi-you/p/16942914.html