SPI(Service Provider Interface)是JDK內置的一種服務提供發現機制,本質是將介面實作類的全限定名配置在檔案中,并由服務加載器讀取組態檔,加載實作類,這樣可以在運行時,動態為介面替換實作類,
在Java中SPI是被用來設計給服務提供商做插件使用的,基于策略模式來實作動態加載的機制,我們在程式只定義一個介面,具體的實作交個不同的服務提供者;在程式啟動的時候,讀取組態檔,由配置確定要呼叫哪一個實作,有很多組件的實作,如日志、資料庫訪問等都是采用這樣的方式,最常用的就是 JDBC 驅動,
1. Java SPI
核心類:java.util.ServiceLoader

服務是一組眾所周知的介面和(通常是抽象的)類,服務提供者是服務的特定實作,提供者中的類通常實作介面,并子類化服務本身中定義的類,服務提供者可以以擴展的形式安裝在Java平臺的實作中,即放置在任何常見擴展目錄中的jar檔案,提供程式也可以通過將它們添加到應用程式的類路徑或其他特定于平臺的方法來提供,
通過在資源目錄META-INF/services中放置一個提供程式組態檔來識別服務提供程式,檔案名是服務型別的完全限定二進制名稱,該檔案包含具體提供程式類的完全限定二進制名的串列,每行一個,每個名稱周圍的空格和制表符以及空白行將被忽略,注釋字符是'#';在每一行中,第一個注釋字符之后的所有字符都將被忽略,檔案必須用UTF-8編碼,
按照上面的方法,我們來寫個例子試一下
首先,定義一個介面Car
package org.example; public interface Car { void run(); }
兩個實作類
ToyotaCar.java
package org.example; public class ToyotaCar implements Car { @Override public void run() { System.out.println("Toyota"); } }
HondaCar.java
package org.example; public class HondaCar implements Car { @Override public void run() { System.out.println("Honda"); } }
在META-INF/services下創建一個名為org.example.Car的文本檔案
org.example.ToyotaCar org.example.HondaCar
最后,寫個測驗類運行看一下效果
package org.example; import java.util.ServiceLoader; public class App { public static void main( String[] args ) { ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class); serviceLoader.forEach(x->x.run()); } }

跟一下ServiceLoader的代碼,看看是怎么找到服務實作的

用當前執行緒的類加載器加載

介面和類加載器都有了,萬事俱備只欠東風



Java SPI 不足之處:
- 不能按需加載,Java SPI在加載擴展點的時候,會一次性加載所有可用的擴展點,很多是不需要的,會浪費系統資源
- 獲取某個實作類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據某個引數來獲取對應的實作類
- 不支持AOP與IOC
- 如果擴展點加載失敗,會導致呼叫方報錯,導致追蹤問題很困難
2. Dubbo SPI
Dubbo重新實作了一套功能更強的SPI機制, 支持了AOP與依賴注入,并且利用快取提高加載實作類的性能,同時支持實作類的靈活獲取,
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.8</version> </dependency>
核心類:org.apache.dubbo.common.extension.ExtensionLoader
先來了解一下@SPI注解,@SPI是用來標記介面是一個可擴展的介面
改造一下前面的例子,在Car介面上加上@SPI注解
package org.example; import org.apache.dubbo.common.extension.SPI; @SPI public interface Car { void run(); }
兩個實作類不變
在META-INF/dubbo目錄下創建名為org.example.Car的文本檔案,內容如下(鍵值對形式):
toyota=org.example.ToyotaCar honda=org.example.HondaCar
撰寫測驗類
package org.example; import org.apache.dubbo.common.extension.ExtensionLoader; import java.util.ServiceLoader; public class App { public static void main( String[] args ) { // Java SPI ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class); serviceLoader.forEach(x->x.run()); // Dubbo SPI ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); Car car = extensionLoader.getExtension("honda"); car.run(); } }
下面跟一下代碼


如果快取Map中有,直接回傳,沒有則加載完以后放進去





加載策略到底是怎樣的呢?



到這里就有點明白了,又看到了熟悉的ServiceLoad.load(),這不是剛才講的Java SPI嘛




回到之前策略那個地方,將策略按順序排列,依次遍歷所有的策略來加載,就是在那三個目錄下查找指定的檔案,并讀取其中的內容

跟之前的ServiceLoader如出一轍

遇到@Adaptive標注的就快取起來

下課
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/254328.html
標籤:其他
