Java SPI用法案例詳解
1.什么是SPI
SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現(xiàn)或者擴展的接口,它可以用來啟用框架擴展和替換組件。 SPI的作用就是為這些被擴展的API尋找服務(wù)實現(xiàn)。
2.SPI和API的使用場景
API (Application Programming Interface)在大多數(shù)情況下,都是實現(xiàn)方制定接口并完成對接口的實現(xiàn),調(diào)用方僅僅依賴接口調(diào)用,且無權(quán)選擇不同實現(xiàn)。 從使用人員上來說,API 直接被應(yīng)用開發(fā)人員使用。
SPI (Service Provider Interface)是調(diào)用方來制定接口規(guī)范,提供給外部來實現(xiàn),調(diào)用方在調(diào)用時則選擇自己需要的外部實現(xiàn)。 從使用人員上來說,SPI 被框架擴展人員使用。
3.SPI的簡單實現(xiàn)
下面我們來簡單實現(xiàn)一個jdk的SPI的簡單實現(xiàn)。
首先第一步,定義一組接口:
public interface UploadCDN { void upload(String url); }
這個接口分別有兩個實現(xiàn):
public class QiyiCDN implements UploadCDN { //上傳愛奇藝cdn @Override public void upload(String url) { System.out.println("upload to qiyi cdn"); } } public class ChinaNetCDN implements UploadCDN {//上傳網(wǎng)宿cdn @Override public void upload(String url) { System.out.println("upload to chinaNet cdn"); } }
然后需要在resources目錄下新建META-INF/services目錄,并且在這個目錄下新建一個與上述接口的全限定名一致的文件,在這個文件中寫入接口的實現(xiàn)類的全限定名:
這時,通過serviceLoader加載實現(xiàn)類并調(diào)用:
public static void main(String[] args) { ServiceLoader<UploadCDN> uploadCDN = ServiceLoader.load(UploadCDN.class); for (UploadCDN u : uploadCDN) { u.upload("filePath"); } }
輸出如下:
這樣一個簡單的spi的demo就完成了??梢钥吹狡渲凶顬楹诵牡木褪峭ㄟ^ServiceLoader這個類來加載具體的實現(xiàn)類的。
4. SPI原理解析
通過上面簡單的demo,可以看到最關(guān)鍵的實現(xiàn)就是ServiceLoader這個類,可以看下這個類的源碼,如下:
public final class ServiceLoader<S> implements Iterable<S> { //掃描目錄前綴 private static final String PREFIX = "META-INF/services/"; // 被加載的類或接口 private final Class<S> service; // 用于定位、加載和實例化實現(xiàn)方實現(xiàn)的類的類加載器 private final ClassLoader loader; // 上下文對象 private final AccessControlContext acc; // 按照實例化的順序緩存已經(jīng)實例化的類 private LinkedHashMap<String, S> providers = new LinkedHashMap<>(); // 懶查找迭代器 private java.util.ServiceLoader.LazyIterator lookupIterator; // 私有內(nèi)部類,提供對所有的service的類的加載與實例化 private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; String nextName = null; //... private boolean hasNextService() { if (configs == null) { try { //獲取目錄下所有的類 String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { //... } //.... } } private S nextService() { String cn = nextName; nextName = null; Class<?> c = null; try { //反射加載類 c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { } try { //實例化 S p = service.cast(c.newInstance()); //放進緩存 providers.put(cn, p); return p; } catch (Throwable x) { //.. } //.. } } }
上面的代碼只貼出了部分關(guān)鍵的實現(xiàn),有興趣的讀者可以自己去研究,下面貼出比較直觀的spi加載的主要流程供參考:
5.dubbo SPI
dubbo作為一個高度可擴展的rpc框架,也依賴于java的spi,并且dubbo對java原生的spi機制作出了一定的擴展,使得其功能更加強大。
首先,從上面的java spi的原理中可以了解到,java的spi機制有著如下的弊端:
- 只能遍歷所有的實現(xiàn),并全部實例化。
- 配置文件中只是簡單的列出了所有的擴展實現(xiàn),而沒有給他們命名。導(dǎo)致在程序中很難去準確的引用它們。
- 擴展如果依賴其他的擴展,做不到自動注入和裝配。
- 擴展很難和其他的框架集成,比如擴展里面依賴了一個Spring bean,原生的Java SPI不支持。
dubbo的spi有如下幾個概念:
(1)擴展點:一個接口。
(2)擴展:擴展(接口)的實現(xiàn)。
(3)擴展自適應(yīng)實例:其實就是一個Extension的代理,它實現(xiàn)了擴展點接口。在調(diào)用擴展點的接口方法時,會根據(jù)實際的參數(shù)來決定要使用哪個擴展。dubbo會根據(jù)接口中的參數(shù),自動地決定選擇哪個實現(xiàn)。
(4)@SPI:該注解作用于擴展點的接口上,表明該接口是一個擴展點。
(5)@Adaptive:@Adaptive注解用在擴展接口的方法上。表示該方法是一個自適應(yīng)方法。Dubbo在為擴展點生成自適應(yīng)實例時,如果方法有@Adaptive注解,會為該方法生成對應(yīng)的代碼。
dubbo的spi也會從某些固定的路徑下去加載配置文件,并且配置的格式與java原生的不一樣,類似于property文件的格式:
下面將基于dubbo去實現(xiàn)一個簡單的擴展實現(xiàn)。首先,要實現(xiàn)LoadBalance這個接口,當(dāng)然這個接口是被注解標注的可以擴展的:
@SPI("random") public interface LoadBalance { @Adaptive({"loadbalance"}) <T> Invoker<T> select(List<Invoker<T>> var1, URL var2, Invocation var3) throws RpcException; } public class DemoLoadBalance implements LoadBalance { @Override public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { System.out.println("my demo loadBalance is used, hahahahh"); return invokers.get(0);//選擇第一個 } }
然后,需要在duboo SPI的掃描目錄下,添加配置文件,注意配置文件的名稱要和擴展點的接口名稱對應(yīng)起來:
還需要在dubbo的spring配置中顯式的聲明,使用上面自己實現(xiàn)的負載均衡策略:
<dubbo:reference id="helloService" interface="com.dubbo.spi.demo.api.IHelloService" loadbalance="demo" />
然后,啟動dubbo,調(diào)用service,就可以發(fā)現(xiàn)確實是使用了自定義的負載策略:
至此,dubbo的spi的demo也完成了。
dubbo spi的原理和jdk的實現(xiàn)稍有不同,大概流程如下圖,具體的實現(xiàn)讀者可以自己了解下源碼。
6.總結(jié)
關(guān)于spi的詳解到此就結(jié)束了,總結(jié)下spi能帶來的好處:
- 不需要改動源碼就可以實現(xiàn)擴展,解耦。
- 實現(xiàn)擴展對原來的代碼幾乎沒有侵入性。
- 只需要添加配置就可以實現(xiàn)擴展,符合開閉原則。
到此這篇關(guān)于Java SPI用法案例詳解的文章就介紹到這了,更多相關(guān)Java SPI用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java反射學(xué)習(xí) getClass()函數(shù)應(yīng)用
所謂反射,可以理解為在運行時期獲取對象類型信息的操作,本文將詳細介紹,需要的朋友可以參考下2012-12-12解讀@RequestBody與post請求的關(guān)系
這篇文章主要介紹了解讀@RequestBody與post請求的關(guān)系,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12SpringCloud Netfilx Ribbon負載均衡工具使用方法介紹
Ribbon是Netflix的組件之一,負責(zé)注冊中心的負載均衡,有助于控制HTTP和TCP客戶端行為。Spring Cloud Netflix Ribbon一般配合Ribbon進行使用,利用在Eureka中讀取的服務(wù)信息,在調(diào)用服務(wù)節(jié)點時合理進行負載2022-12-12Spring?Boot中使用Swagger3.0.0版本構(gòu)建RESTful?APIs的方法
Swagger?是一個規(guī)范和完整的框架,用于生成、描述、調(diào)用和可視化?RESTful?風(fēng)格的?Web?服務(wù),這篇文章主要介紹了Spring?Boot中使用Swagger3.0.0版本構(gòu)建RESTful?APIs的方法,需要的朋友可以參考下2022-11-11